Android自定义View

        如果想要做出绚丽的界面效果仅仅靠系统的控件是远远不够的,这个时候就必须通过自定义View来实现这些绚丽的效果。自定义View是一个很难掌握的技术体系,包括View的层次结构、事件分发机制和View的工作原理等细节。自定义View的实现方法有很多,当面对一个自定义View的需求时,需要灵活地分析从而找到最高效的方法。

        自定义View中有些问题如果处理不好会影响View的正常使用或者导致内存泄漏。大概需要注意一下几点:

·让View支持wrap_parent

     直接继承自View或者ViewGroup的控件,如果不在onMearsure中对wrap_content做特殊处理,在布局中使用wrap_parent时将无法产生想要的效果。这在View的工作原理(一)中有详细解释,直接继承View的自定义控件需要重写onMearsure方法并设置wrap_content时自身的大小,否则使用wrap_content时相当于使用march_parent。为什么这么说,需要结合SpecMode解释,当View使用wrap_content时他的SpecMode也就是最大模式,这种情况下View的specSize是父容器中目前剩余的大小,很显然View的宽高等于父容器当前剩余的大小,效果和match_parent一致。那么解决方法就是在重写的onMearsure方法中给wrap_content模式的View设置默认宽高就行了。在View的工作原理(一)中有代码示例。

·让View支持padding

        直接继承自View的控件如果不在draw方法中处理padding,那么padding属性是无法起作用的。另外直接继承自ViewGroup的控件需要在onMearsure和onLayout中考虑padding和子元素margin对其造成的影响,否者padding和子元素margin失效。

·尽量不要在View中使用Handler

        View内部本身就提供了post系列的方法,完全可以代替Handler。

·View中如果有线程或者动画,需要及时停止

        如果View变得不可见时我们要及时的停止线程和动画,否者可能造成内存泄漏。

·View带有滑动嵌套的时候需要处理好滑动冲突

      比如一个自定义ViewGroup,其中有很多子元素,子元素可以左右滑动也可以上下滑动,就产生了滑动冲突。如果不进行处理,会严重影响View的效果。

下面是4种自定义View的分类,当然仁者见仁智者见智,自定义View的标准并不唯一。

1、 继承View重写onDraw方法

        这种方法主要用来实现一些不规则的效果,也就是这种效果不方便通过布局的组合方式来达到,往往需要静态或者动态的显示一些不规则图案。这需要重写onDraw方法,需要自己支持wrap_content(为wrap_content指定默认值)并处理padding。其次有必要的话还需要对外提供自定义属性。下面先实现一个简单的自定义View,绘制一个红色的实心圆

public class CircleView extends View {

    private int mColor =Color.RED;

    private Paint mPaint = newPaint(Paint.ANTI_ALIAS_FLAG);

 

    public CircleView(Contextcontext) {

        super(context);

        init();

    }

 

    public CircleView(Contextcontext, AttributeSet attrs) {

        this(context, attrs,0);

    }

 

    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();

    }

 

    private void init() {

       mPaint.setColor(mColor);

    }

 

    @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 intpaddingBottom = 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);

    }

}

        从代码的onMearsure方法中可以看出,当宽或者高的SpecMode 是MeasureSpec.AT_MOST(wrap_content),就为其指定默认大小200px。在onDraw方法中绘制的时候考虑View四周的留白,包括圆心和半径都会考虑View四周的padding。

       很多情况下我们还需要指定自定义属性,遵循如下几步

·第一步,在value目录下创建自定义属性的XML,一般文件名采用attrs开头。这里命名为attrs.xml

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

<resources>

   <declare-styleable name="CircleView">

       <attr name="circle_color" format="color" />

   </declare-styleable>

</resources>

         文件中声明了一个自定义属性集合CircleView,这里就添加了一个属性,当然还可以继续加,不过都遵循这个格式。里面是一个格式为color的属性circle_color,格式color指的就是颜色。自定义属性还能有很多格式,比如reference(指id)、dimension(指尺寸)和int等基本数据类型。

·第二步在View的构造方法中解析自定义属性的值并做相处相应的处理。这里我们就需要解析circle_color这个属性的值了。代码在上面的自定义View中标记为红色了。首先加载自定义属性集合"CircleView,接着解析circle_color属性,这个属性的id是R.styleable.CircleView_circle_color,Color.RED是为其设定的在使用中的默认属性, 用的时候可以修改。最后通过recycle()释放资源

·第三步,在布局文件中使用自定义属性

<LinearLayoutxmlns: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.ryg.myview.ui.CircleView

       android:id="@+id/circleView1"

       android:layout_width="wrap_content"

        android:layout_height="100dp"

       android:layout_margin="20dp"

       android:background="#000000"

       android:padding="20dp"

       app:circle_color="@color/light_green"/>

</LinearLayout>

        这里要注意两点,如果使用自定义属性,必须在布局文件添加shemas声明如红色代码所示。这个声明中app是自定义的的属性前缀,也可以用其他名字。使用自定义属性的前缀必须和shemas声明的自定义的属性前缀相同,如黄色背景代码所示。

 

2、 继承特定的View(比如TextView)

        这种方法比较简单,一般用于扩展某种已有的View功能。也不需要自己支持wrap_content和padding等。不再详述。

3、 继承ViewGroup派生特殊的Layout

        采用这种方式要注意你要实现的是一个和LinearLayout这一层次的View,过程会很复杂。所以可以补实现他的方方面面,仅仅完成主要功能就可以了。想必你已经想到需要做哪些工作了。你需要合适的处理ViewGroup中的测量、布局两个过程,并同时处理子元素的测量和布局过程。一位百度工程师在他的github上分享过很一段完整的自定义Layout,感兴趣的可以参见https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_3/src/com/ryg/chapter_3/ui/HorizontalScrollViewEx.java

4、 继承特定的ViewGroup(比如LinearLayout)

        采用这种方法不需要自己处理测量和布局的过程。一般来说方法2能实现的方法4也能实现。只不过方法二更接近底层一点。在方法1给出范例,然后参见相关View源码即可完成2和4的自定义View,不再介绍了。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值