自定义View

自定义View的分类

  1. 继承View重写onDraw方法
    这种方法主要用于实现一些不规则的效果,即这种效果不方便通过布局的组合方式来达到,旺旺需要静态或者动态的显示一些不规则的图形。很显然这种需要通过绘制的方式来实现,即重写onDraw方法。采用这种方式需要自己支持wrap_content,并且padding也需要自己处理。
  2. 继承ViewGroup派生特殊的Layout
    这种方法主要用于实现自定义布局,即除了LinearLayout、RelativeLayout、FrameLayout这几种系统的布局之外,我们重新定义一种新布局,当某种效果看起来很像几种View组合在一起的时候,可以采用这种方法来实现。
  3. 继承特定的View(比如TextView)
    这种方法比较常见,一般是用于扩展某种已有的View功能,比如TextView,这种方法比较容易实现。这种方法不需要自己支持wrap_content和padding等。
  4. 继承特定的ViewGroup(比如LinearLayout)
    这种方法也比较常见,当某种效果看起来很像几种View组合在一起的时候,可以采用这种方法来实现。采用这种方法不需要自己处理ViewGroup的测量和布局这两个过程。需要注意这种方法和方法2的区别,一般来说方法2能实现的效果方法方法4也能实现,两者主要差别在与方法2更接近View的底层。

自定义View须知

  1. 让View支持wrap_content
    这是因为直接继承View或者ViewGroup的控件,如果不在onMeasure中对wrap_content做特殊处理,那么当外界在布局中使用wrap_content时就无法达到预期效果。
  2. 如果有必要,让你的View支持padding
    这是因为直接继承View的控件,如果不在draw方法中处理padding,那么padding属性是无法起作用的。另外,直接继承自ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响,不然将导致padding和子元素的margin失效。
  3. 尽量不要在View中使用Handler,没必要
    这是因为View内部本身就提供了post系列的方法,完全可以替代Handler的使用,当然除非你很明确地要使用Handler来发送消息。
  4. View中如果有线程或者动画,需要及时停止。
    如果线程和动画需要停止时,那么onDetachedFromWindow是一个很好的时机。当包含此View的Activity退出或者当前View被remove时,View的onDetachedFromWindow方法会被调用,和此方法对应的是onAttachedToWindow,当包含此View的Activity启动时,View的onAttachedToWindow方法会被调用。同时,当View变得不可见时我们也需要停止线程和动画,如果不及时处理这种问题,有可能会造成内存泄漏。
  5. View带有滑动嵌套情形时,需要处理好滑动冲突
    如果有滑动冲突的话,那么要合适的处理滑动冲突,否则将会影响View的效果。

自定义View示例

  1. 继承View重写onDraw方法
public class CircleView extends View {

    private int mColor = Color.RED;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public CircleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleView(Context context) {
        this(context, null);
    }

    private void init() {
        mPaint.setColor(mColor);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        int width = getWidth() - paddingLeft - paddingRight;
        int height = getHeight() - paddingTop - paddingBottom;
        int radius = Math.min(width, height) / 2;
        canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2, radius, mPaint);
    }

}

上面的代码实现了一个具有圆形效果的自定义View,他会在自己的中心店以宽高的最小值为直径绘制一个红色的实心圆。
除了系统自带的属性,我们还可以增加自定义属性:
第一步,在values目录下创建自定义属性的XML,比如attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="CircleView">
        <attr name="circle_color" format="color" />
    </declare-styleable>

</resources>

在XML中声明了一个自定义属性集合CircleView,在这个集合里面可以有很多自定义属性,这里只自定义了一个格式为”color”的属性”circle_color”,这里的格式color指的是颜色。除了颜色格式,自定义属性还有其他格式,比如reference是指资源id,dimension是指尺寸,而像string、integer和boolean这种是指基本数据类型。具体的可以查一下文档,并没有什么难度。
第二部,在View的构造方法中解析自定义属性的值并做相应的处理。

public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
        a.recycle();
        init();
    }

看起来很简单,首先加载自定义属性集合CircleView,接着解析CircleView属性集合中的circle_color属性,他的id为R.styleable.CircleView_circle_color。在这一步骤中,如果在使用时没有指定circle_color这个属性,那么就会选择红色为默认值,解析完毕后通过recycle方法来释放资源,这样CircleView中所作的工作就完成了。

<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:background="#ffffff"
    android:orientation="vertical" >

    <com.example.testandroid.view.CircleView
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:layout_margin="20dp"
        android:background="#000000"
        android:padding="20dp"
        app:circle_color="#717171" />

</LinearLayout>

在XML中使用自定义属性,如上所示,其中还有一点需要注意,首先,为了使用自定义属性,必须在布局文件中添加schemas声明:xmlns:app=”http://schemas.android.com/apk/res-auto”。在这个声明中,app是自定义属性前缀,当然可以换成其他名字,但是CircleView中的自定义属性的前缀必须和这里的一致,然后就可以在CircleView中使用自定义属性了。
下面是CircleView完成代码

public class CircleView extends View {

    private int mColor = Color.RED;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
        a.recycle();
        init();
    }

    public CircleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleView(Context context) {
        this(context, null);
    }

    private void init() {
        mPaint.setColor(mColor);
        mPaint.setAntiAlias(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSPecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSPecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(200, 200);
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(200, heightSpecSize);
        } else if (heightSPecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, 200);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        int width = getWidth() - paddingLeft - paddingRight;
        int height = getHeight() - paddingTop - paddingBottom;
        int radius = Math.min(width, height) / 2;
        canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2, radius, mPaint);
    }

}

2.继承ViewGroup派生特殊的Layout
这种方法主要用于实现自定义的布局,采用这种方式稍微复杂一些,需要合适地处理ViewGroup的测量、布局这两个过程,并同时处理子元素的测量和布局过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值