自定义View入门必看

Android本身会给我们提供一些基本的view控件,但是当我们要想做出绚丽的效果时,单靠这些基础的控件就显得力不从心了,就需要我们自定义view了,大部分开发者都会对自定义view有种恐惧感,确实,自定一个view有一定的难度,它需要我们对view事件分发体系 、view工作原理有一定了解,当然了,你对这些基础知识不怎么了解,你按照例子照搬照抄有时也能达到目的,但那可能是你自定义的view实现的功能逻辑比较简单,如果逻辑复杂的话,照猫画虎可就不是一种可取的方法了,所以建议不熟悉view事件分发体系 、view工作原理的开发者,最好还是多了解一下,这些原理看似底层,但是对于实际开发中处理遇到的问题会有很多帮助的。接下来我们就开始自定义view的入门级学习吧!

自定义view一般分为以下四种情况:

  1. 直接继承View
  2. 直接继承ViewGroup
  3. 继承特定的ViewGroup(如LinearLayout)
  4. 继承特定的View(如TextView,ImageView)

    下面我们就针对这四种情况分别说说需要注意的问题吧!
    ① 直接继承View的自定义view,当你将view的宽或高设置为wrap_content的时候,其实相当于将其设置为match_parent(具体原理涉及到view的measure过程),我们需要手动的处理这种情况,给定一个默认值;padding属性将会失效,需要你手动处理,margin属性不会失效的,因为margin属性是由父view管理实现的。
    ②直接继承ViewGroup的自定义view,它跟上面的情况有同样的问题,额外的,它还需要对子view的margin和padding属性手动实现支持。
    ③继承特定的ViewGroup的自定义view,它与第二种方法相比,没有那些需要额外处理的问题。一般来说,继承特定的ViewGroup都能实现直接继承ViewGroup所实现的功能,只不过第二种方式更接近底层。我个人还是推荐这种方式,毕竟需要你处理的事情少了,出错的概率也会相应的降低。
    ④继承特定的View,也没有上面说的那些问题我们需要额外处理的问题,这种就更简单了,我们需要的就是重写view的方法,来完成我们自定义的功能逻辑。

下面我们拿一个具体的例子来说明一下,自定义一个圆形view,并支持自定义view的自定义属性:

circlr_view_attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleView">
        <attr name="background_color" format="color" />
    </declare-styleable>
</resources>

text.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="horizontal">

    <com.zb.viewevent.CircleView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="20dp"
        app:background_color="#ff0000" />

</LinearLayout>

CircleView:

package com.zb.viewevent;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

public class CircleView extends View {

    private Context context;
    private Paint paint;
    private int backgroundColor;

    public CircleView(Context context) {
        super(context);
        init(context);
    }

    public CircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        backgroundColor = typedArray.getColor(R.styleable.CircleView_background_color, Color.parseColor("#000000"));
        init(context);
    }

    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        backgroundColor = typedArray.getColor(R.styleable.CircleView_background_color, Color.parseColor("#000000"));
        init(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //默认宽高值(px)
        int defaultWidthSize = 200;
        int defaultHeightSize = 200;
        //MeasureSpec.AT_MOST 对应于xml文件中的wrap_content
        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(defaultWidthSize, defaultHeightSize);
        } else if (widthMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(defaultWidthSize, heightSize);
        } else if (heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSize, defaultHeightSize);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        int paddingLeft = getPaddingLeft();
        int paddingTop = getPaddingTop();
        int paddingRight = getPaddingRight();
        int paddingBottom = getPaddingBottom();
        int realWidth = getWidth() - paddingLeft - paddingRight;
        int realHeight = getHeight() - paddingTop - paddingBottom;
        //以最小值作为圆的半径
        int r = Math.min(realWidth, realHeight);
        canvas.drawCircle(paddingLeft + realWidth, paddingTop + realHeight, r, paint);
    }

    public void init(Context context) {
        this.context = context;
        paint = new Paint();
        paint.setColor(backgroundColor);
    }
}

一个支持wrap_content、padding属性和自定义属性的自定义view就完成了,虽然实现的功能逻辑简单,但是,我们要明白一点的就是,自定义view有无数种,只需要掌握了实现思路,那么问题就会迎刃而解了。

实现复杂的自定义还需走很长路,需要我们一步一步慢慢往前走。

猜你喜欢

转载自blog.csdn.net/qq_15692039/article/details/66479806