Android自定义View

250 阅读2分钟

1.自定义属性

attr.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyProgressBar">
        <attr name="android:color" />
        <attr name="android:lineHeight" />
        <attr name="android:radius" />
        <attr name="android:progress" />
        <attr name="android:textSize" />
    </declare-styleable>
</resources>

2.MyProgressBar

测量保存是固定写法

class MyProgressBar : View {
    private var mRadius = 0
    private var mColor = 0
    private var mLineHeight = 0
    private var mTextSize = 0
    private var mProgress = 0
    private val mPaint: Paint by lazy { Paint() }

    constructor(context: Context?) : super(context)
    constructor(
        context: Context,
        attrs: AttributeSet?
    ) : super(context, attrs) {
        //TypedArray 获取layout中属性
        val ta = context.obtainStyledAttributes(attrs, R.styleable.MyProgressBar)
        mRadius =
            ta.getDimension(R.styleable.MyProgressBar_android_radius, Util.dp2px(30, this)).toInt()
        mColor = ta.getColor(R.styleable.MyProgressBar_android_color, -0x10000)
        mLineHeight =
            ta.getDimension(
                R.styleable.MyProgressBar_android_lineHeight, Util.dp2px(3, this)
            ).toInt()
        mTextSize =
            ta.getDimension(R.styleable.MyProgressBar_android_textSize, Util.dp2px(36, this))
                .toInt()
        mProgress = ta.getInt(R.styleable.MyProgressBar_android_progress, 30)
        //TypedArray池化,需要回收(频繁的创建array会影响性能)
        ta.recycle()
        //画笔
        mPaint.isAntiAlias = true
        mPaint.color = mColor
        //只描边不填充
        mPaint.style = Paint.Style.STROKE
        //描边的宽度
        mPaint.strokeWidth = mLineHeight * 1.0f / 4
    }

    //MyViewActivity定义了点击后关联属性progress的动画
    var progress: Int
        //layout xml中获取的progress
        get() = mProgress
        //MyViewActivity定义的属性progress赋值给typerarray的mprogress,再重绘
        set(progress) {
            mProgress = progress
            //重绘,调用ondraw方法
            invalidate()
        }

    //测量,固定写法
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val needWidth = measureWidth() + paddingLeft + paddingRight
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)
        val needHeight = measureHeight() + paddingTop + paddingBottom
        //EXACTLY=精确,AT_MOST=wrap content,UNSPECIFIED=子类想要多大就可以多大
        val width = when (widthMode) {
            MeasureSpec.EXACTLY -> widthSize
            MeasureSpec.AT_MOST -> min(needWidth, widthSize)
            MeasureSpec.UNSPECIFIED -> needWidth
            else -> min(needWidth, widthSize)
        }
        val height = when (heightMode) {
            MeasureSpec.EXACTLY -> heightSize
            MeasureSpec.AT_MOST -> min(needHeight, heightSize)
            MeasureSpec.UNSPECIFIED -> needHeight
            else -> min(needHeight, heightSize)
        }
        setMeasuredDimension(width, height)
    }

    private fun measureHeight(): Int {
        return mRadius * 2
    }

    private fun measureWidth(): Int {
        return mRadius * 2
    }

    //ondraw->画布->画笔
    override fun onDraw(canvas: Canvas) {
        //画细圆
        canvas.drawCircle(
            width / 2.toFloat(), height / 2.toFloat(),
            width / 2 - paddingLeft - mPaint.strokeWidth / 2, mPaint
        )
        //移动画布
        canvas.translate(paddingLeft.toFloat(), paddingTop.toFloat())
        //画粗圆
        mPaint.strokeWidth = mLineHeight.toFloat()
        //根据progress绘制的粗圆
        val angle = mProgress * 1.0f / 100 * 360
        canvas.drawArc(
            RectF(
                0.0f, 0.0f, (width - paddingLeft * 2).toFloat(),
                (height - paddingLeft * 2).toFloat()
            ), 0f, angle, false, mPaint
        )
        val text = "$mProgress%"
        //        text = "张鸿洋";
        //画字的这只笔,只描边不填充
        mPaint.strokeWidth = 0f
        //这只笔在定义的x,y上水平居中
        mPaint.textAlign = Paint.Align.CENTER
        mPaint.textSize = mTextSize.toFloat()

        //获得文字的高度
        val bound = Rect()
        mPaint.getTextBounds(text, 0, text.length, bound)
        val textHeight = bound.height()

        //绘制text,索引0到text.length,原点为1/2宽-移动距离,1/2高+1/2文字高度-移动距离,画笔
        canvas.drawText(
            text, 0, text.length,
            +width / 2 - paddingLeft.toFloat(),
            height / 2 - paddingTop + textHeight / 2.toFloat(), mPaint
        )
        //这里是静态效果,点击后动态效果是写在MyViewActivity中的动画
    }

    //存储与恢复,xml中需添加id,固定写法
    override fun onSaveInstanceState(): Parcelable? {
        val bundle = Bundle()
        bundle.putInt("key_progress", mProgress)
        bundle.putParcelable("instance", super.onSaveInstanceState())
        return bundle
    }

    override fun onRestoreInstanceState(state: Parcelable) {
        if (state is Bundle) {
            val parcelable =
                state.getParcelable<Parcelable>("instance")
            super.onRestoreInstanceState(parcelable)
            mProgress = state.getInt("key_progress")
            return
        }
        super.onRestoreInstanceState(state)
    }
}

3.使用

  <com.******.MyProgressBar
        android:id="@+id/id_pb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:padding="10dp"
        android:progress="0"
        android:textSize="18sp"
        android:color="#ea22e4"
        android:radius="36dp" />
-----------------------------------------------------------------------------
class MyViewActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my_view)
        id_pb.setOnClickListener {
            ObjectAnimator.ofInt(id_pb, "progress", 0, 100).setDuration(3000).start()
        }
    }
}
-----------------------------------------------------------------------------
object Util {
    fun dp2px(dpVal: Int, view: View): Float {
        return TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP, dpVal.toFloat(), view.resources.displayMetrics
        )
    }
}