Android 自定义View(一) 介绍和一个简单TextView显示

来源:互联网 发布:lol网络正常ping高 编辑:程序博客网 时间:2024/05/22 09:00

转载注明出处:http://blog.csdn.net/lb454048898/article/details/50791101

意义

首先知道自定义View的意义,就是Android自身提供的控件已经满足不了我们的需求的时候就会进行继承View来进行自定义View的代码编写,实现需求。

需求

定义需求,是何种,例如 下拉刷新上拉更多的列表 , 看具体展示内容,可继承 GridView 或者 ListView 。

步骤

一般来说,只需要了解最基本的自定义方法即可。

下面介绍几个最普遍的方法:

OnMeasure (测量控件宽高的方法。)

OnDraw (绘制控件内容的方法)

OnLayout (此方法继承ViewGroup用的较多,用于摆放Group里子view的位置)

正题

  1. 定义自定义字节属性,此属性是在布局文件xml里View的属性。
  2. 在View的构造方法中获取属性。
  3. 重写onMeasure方法
  4. 重写OnDraw方法
    这四个步骤是最常见的步骤。

来看一段ImageView的onMeasure方法。

@Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        resolveUri();        int w;        int h;        // Desired aspect ratio of the view's contents (not including padding)        float desiredAspect = 0.0f;        // We are allowed to change the view's width        boolean resizeWidth = false;        // We are allowed to change the view's height        boolean resizeHeight = false;        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);        if (mDrawable == null) {            // If no drawable, its intrinsic size is 0.            mDrawableWidth = -1;            mDrawableHeight = -1;            w = h = 0;        } else {            w = mDrawableWidth;            h = mDrawableHeight;            if (w <= 0) w = 1;            if (h <= 0) h = 1;            // We are supposed to adjust view bounds to match the aspect            // ratio of our drawable. See if that is possible.            if (mAdjustViewBounds) {                resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;                resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;                desiredAspect = (float) w / (float) h;            }        }        int pleft = mPaddingLeft;        int pright = mPaddingRight;        int ptop = mPaddingTop;        int pbottom = mPaddingBottom;        int widthSize;        int heightSize;        if (resizeWidth || resizeHeight) {            /* If we get here, it means we want to resize to match the                drawables aspect ratio, and we have the freedom to change at                least one dimension.             */            // Get the max possible width given our constraints            widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);            // Get the max possible height given our constraints            heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);            if (desiredAspect != 0.0f) {                // See what our actual aspect ratio is                float actualAspect = (float)(widthSize - pleft - pright) /                                        (heightSize - ptop - pbottom);                if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {                    boolean done = false;                    // Try adjusting width to be proportional to height                    if (resizeWidth) {                        int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +                                pleft + pright;                        // Allow the width to outgrow its original estimate if height is fixed.                        if (!resizeHeight && !mAdjustViewBoundsCompat) {                            widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);                        }                        if (newWidth <= widthSize) {                            widthSize = newWidth;                            done = true;                        }                     }                    // Try adjusting height to be proportional to width                    if (!done && resizeHeight) {                        int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +                                ptop + pbottom;                        // Allow the height to outgrow its original estimate if width is fixed.                        if (!resizeWidth && !mAdjustViewBoundsCompat) {                            heightSize = resolveAdjustedSize(newHeight, mMaxHeight,                                    heightMeasureSpec);                        }                        if (newHeight <= heightSize) {                            heightSize = newHeight;                        }                    }                }            }        } else {            /* We are either don't want to preserve the drawables aspect ratio,               or we are not allowed to change view dimensions. Just measure in               the normal way.            */            w += pleft + pright;            h += ptop + pbottom;            w = Math.max(w, getSuggestedMinimumWidth());            h = Math.max(h, getSuggestedMinimumHeight());            widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);            heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);        }        setMeasuredDimension(widthSize, heightSize);    }

里面两种情况,判断width和height的mode是否为 MeasureSpec.EXACTLY,当width 和 height 其中一个为MeasureSpec.EXACTLY的时候进入情况一也就是if (resizeWidth || resizeHeight) 如果不是的情况下,进入情况2 else段。最后将得到的width 和 height 值使用 默认方法 setMeasuredDimension 传入值。

自定义属性值

在values 文件夹里的 任一xml的<resources> 里加上

declare-styleable 标签。记得加上属性name
然后就可以在里面定义属于你自己的自定义属性值

example:

    <attr name="customsText" format="string" />    <attr name="customsSize" format="dimension" />    <declare-styleable name="FirstView">        <attr name="customsText"  />        <attr name="customsColor" format="color" />        <attr name="customsSize"  />    </declare-styleable>

关于declare-styleable里的 attr标签里的属性 format可以参考
declare-styleable中format详解 里的内容;

注意事项

关于自定义View在布局xml里的应用
跟你的工具有关,
在布局xml里最外层的控件都会有这个属性值xmlns:android="http://schemas.android.com/apk/res/android" 这个属性值就是获取Android本身自带的xml属性值。xmlns:android 这个android就是属性值开头例如:android:width等。
正常添加自己定义的属性值都是xmlns:xxxx="http://schemas.android.com/apk/res/包名
如eclipse 如下:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:custom="http://schemas.android.com/apk/viewdemo.cifer.com.myview"    android:layout_width="match_parent"    android:layout_height="match_parent">    <viewdemo.cifer.com.myview.view.FirstView        android:layout_width="wrap_content"        android:layout_height="wrap_content"        custom:customsText="3712"        android:padding="10dp"        custom:customsColor="#ff0000"        android:layout_centerInParent="true"        custom:customsSize="40sp" /></RelativeLayout>

不过在Android Studio里这样写,会导致程序crash。需要将获取自定义属性的地址改成xmlns:custom="http://schemas.android.com/apk/res-auto" 即可,所有的自定义属性都会出来再custom:里。
example

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:custom="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent">    <viewdemo.cifer.com.myview.view.FirstView        android:layout_width="wrap_content"        android:layout_height="wrap_content"        custom:customsText="3712"        android:padding="10dp"        custom:customsColor="#ff0000"        android:layout_centerInParent="true"        custom:customsSize="40sp" /></RelativeLayout>

构造方法

这里没什么说的,唯一需要注意的是构造方法的前两个的引用不是super 而是this ;

public FirstView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public FirstView(Context context) {        this(context, null);    }    public FirstView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.FirstView, defStyleAttr, 0);//在这里有两种获取方法,我都列出来,我自己用的第二种,可以方便控制是否有此属性,进行获取.注:构造方法只会调用一次。        int n = typedArray.getIndexCount();        for (int i = 0; i < n; i++) {            int attr = typedArray.getIndex(i);            switch (attr) {                case R.styleable.FirstView_customsText:                    break;                case R.styleable.FirstView_customsColor:                    // 默认颜色设置为黑色                    break;                case R.styleable.FirstView_customsSize:                    // 默认设置为16sp,TypeValue也可以把sp转化为px                    break;            }        }        try {            mTitleText = typedArray.getString(R.styleable.FirstView_customsText);            mTitleTextColor = typedArray.getColor(R.styleable.FirstView_customsColor, Color.BLACK);            mTitleTextSize = typedArray.getDimensionPixelSize(R.styleable.FirstView_customsSize, (int) TypedValue.applyDimension(                    TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));        } catch (Exception e) {            e.printStackTrace();        } finally {            typedArray.recycle();        }        /**         * 获得绘制文本的宽和高         */        mPaint = new Paint();        mPaint.setTextSize(mTitleTextSize);        mPaint.setColor(mTitleTextColor);        mBound = new Rect();        mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);    }

注意一下,在构造方法里获得的属性值是布局文件xml里,可以在View类里写入方法修改如

public void setText(String text){ this.mTitleText = text; postInvalidate();//invalidate();}

关于View类自带方法的 postInvalidate() 和 invalidate() 的区别可以参考android中Invalidate和postInvalidate的区别

重写onMeasure方法

上面亮出了ImageView的onMeasure方法,觉得很复杂,正常来说一般不过几十行代码,就是计算内容的width和height值,与系统默认的width 和 height值,进行比较,根据布局文件xml里width/height 的设置取最小值or最大值。
onMeasure获取到他的MeasureSpec的模式。具体的内容可以查看MeasureSpec介绍 的内容。

看一下简单的重写onMeasure方法

 @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int width = 0;        int height = 0;        /**         * 设置宽度         */        int specMode = MeasureSpec.getMode(widthMeasureSpec);        int specSize = MeasureSpec.getSize(widthMeasureSpec);        switch (specMode) {            case MeasureSpec.EXACTLY:// 明确指定了                width = getPaddingLeft() + getPaddingRight() + specSize;                break;            case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT                width = getPaddingLeft() + getPaddingRight() + mBound.width();                break;        }        /**         * 设置高度         */        specMode = MeasureSpec.getMode(heightMeasureSpec);        specSize = MeasureSpec.getSize(heightMeasureSpec);        switch (specMode) {            case MeasureSpec.EXACTLY:// 明确指定了                height = getPaddingTop() + getPaddingBottom() + specSize;                break;            case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT                height = getPaddingTop() + getPaddingBottom() + mBound.height();                break;        }        setMeasuredDimension(width, height);    }

onDraw

这里较为重要,因为界面的呈现效果,和界面美化,动画全在这里做,也是更新最频繁的方法。

简单的onDraw方法

@Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPaint.setColor(Color.BLACK);        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);        mPaint.setColor(mTitleTextColor);        canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);    }

更多方法

onFinishInflate() 当View中所有的子控件 均被映射成xml后触发onSizeChanged(int, int, int, int)   改变大小时调用。 参数分别为 width ,height , oldWidth , oldHeightonTrackballEvent(MotionEvent)   轨迹球事件onTouchEvent(MotionEvent)   触屏事件onFocusChanged(boolean, int, android.graphics.Rect)  当View获取 或失去焦点时触发 onWindowFocusChanged(boolean)    当view被附着到一个窗口时触发onDetachedFromWindow() 当view离开附着的窗口时触发,该方法和  onAttachedToWindow() 是相反的。onWindowVisibilityChanged(int) 当窗口中包含的可见的view发生变化时触发以及常见的onKeyDown(int, KeyEvent)     onKeyUp(int, KeyEvent)   

更多资料参考
Android.View绘制流程 来自 - WPJY
Android View绘制流程及invalidate()等方法分析 来自- qinjuning
Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析 来自 - 老罗(罗升阳)

0 0
原创粉丝点击