Android自定义View

    xiaoxiao2021-03-25  154

            如果想要做出绚丽的界面效果仅仅靠系统的控件是远远不够的,这个时候就必须通过自定义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,不再介绍了。

    转载请注明原文地址: https://ju.6miu.com/read-2252.html

    最新回复(0)