zoukankan      html  css  js  c++  java
  • Android自定义View

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24252901

    很多的Android入门程序猿来说对于Android自定义View,可能都是比较恐惧的,但是这又是高手进阶的必经之路,所有准备在自定义View上面花一些功夫,多写一些文章。先总结下自定义View的步骤:

    1、自定义View的属性

    2、在View的构造方法中获得我们自定义的属性

    [ 3、重写onMesure ]

    4、重写onDraw

    我把3用[]标出了,所以说3不一定是必须的,当然了大部分情况下还是需要重写的。

    1、自定义View的属性,首先在res/values/  下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。

    <?xml version="1.0" encoding="utf-8"?>  
    <resources>  
      
        <attr name="titleText" format="string" />  
        <attr name="titleTextColor" format="color" />  
        <attr name="titleTextSize" format="dimension" />  
      
        <declare-styleable name="CustomTitleView">  
            <attr name="titleText" />  
            <attr name="titleTextColor" />  
            <attr name="titleTextSize" />  
        </declare-styleable>  
      
    </resources>  

    我们定义了字体,字体颜色,字体大小3个属性,format是值该属性的取值类型:

    一共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag;不清楚的可以google一把。

    然后在布局中声明我们的自定义View

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent" >  
      
        <com.example.customview01.view.CustomTitleView  
            android:layout_width="200dp"  
            android:layout_height="100dp"  
            custom:titleText="3712"  
            custom:titleTextColor="#ff0000"  
            custom:titleTextSize="40sp" />  
      
    </RelativeLayout>  

    一定要引入 xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"我们的命名空间,后面的包路径指的是项目的package

    2、在View的构造方法中,获得我们的自定义的样式

      /**
         * 文本
         */
        private String mTitleText;
        /**
         * 文本的颜色
         */
        private int mTitleTextColor;
        /**
         * 文本的大小
         */
        private int mTitleTextSize;
    
        /**
         * 绘制时控制文本绘制的范围
         */
        private Rect mBound;
        private Paint mPaint;
    
        public CustomTitleView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public CustomTitleView(Context context) {
            this(context, null);
        }
    
        /**
         * 获得我自定义的样式属性
         * 
         * @param context
         * @param attrs
         * @param defStyle
         */
        public CustomTitleView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            /**
             * 获得我们所定义的自定义样式属性
             */
            TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);
            int n = a.getIndexCount();
            for (int i = 0; i < n; i++) {
                int attr = a.getIndex(i);
                switch (attr) {
                case R.styleable.CustomTitleView_titleText:
                    mTitleText = a.getString(attr);
                    break;
                case R.styleable.CustomTitleView_titleTextColor:
                    // 默认颜色设置为黑色
                    mTitleTextColor = a.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.CustomTitleView_titleTextSize:
                    // 默认设置为16sp,TypeValue也可以把sp转化为px
                    mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
                    break;
                }
            }
            a.recycle();
            /**
             * 获得绘制文本的宽和高
             */
            mPaint = new Paint();
            mPaint.setTextSize(mTitleTextSize);
            // mPaint.setColor(mTitleTextColor);
            mBound = new Rect();
            mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
        }

    我们重写了3个构造方法,默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造调用我们的三个参数的构造,我们在三个参数的构造中获得自定义属性。

    3、我们重写onDraw,onMesure调用系统提供的

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

    此时的效果是:

    是不是觉得还不错,基本已经实现了自定义View。但是此时如果我们把布局文件的宽和高写成wrap_content,会发现效果并不是我们的预期:

    系统帮我们测量的高度和宽度都是MATCH_PARNET,当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。

    所以,当设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMesure方法”:

    重写之前先了解MeasureSpec的specMode,一共三种类型:

    EXACTLY:一般是设置了明确的值或者是MATCH_PARENT

    AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT

    UNSPECIFIED:表示子布局想要多大就多大,很少使用

    下面是我们重写onMeasure代码:

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
            int width;
            int height;
            if (widthMode == MeasureSpec.EXACTLY) {
                width = widthSize;
            } else {
                mPaint.setTextSize(mTitleTextSize);
                mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
                float textWidth = mBounds.width();
                int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
                width = desired;
            }
    
            if (heightMode == MeasureSpec.EXACTLY) {
                height = heightSize;
            } else {
                mPaint.setTextSize(mTitleTextSize);
                mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
                float textHeight = mBounds.height();
                int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
                height = desired;
            }
    
            setMeasuredDimension(width, height);
        }

    现在我们修改下布局文件:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent" >  
      
        <com.example.customview01.view.CustomTitleView  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            custom:titleText="3712"  
            android:padding="10dp"  
            custom:titleTextColor="#ff0000"  
            android:layout_centerInParent="true"  
            custom:titleTextSize="40sp" />  
      
    </RelativeLayout>  

    现在的效果是:

    完全复合我们的预期,现在我们可以对高度、宽度进行随便的设置了,基本可以满足我们的需求。

    当然了,这样下来我们这个自定义View与TextView相比岂不是没什么优势,所有我们觉得给自定义View添加一个事件:

    在构造中添加:

    this.setOnClickListener(new OnClickListener() {  
           @Override  
            public void onClick(View v)   {  
                mTitleText = randomText();  
                postInvalidate();  
            }  
    });  
        private String randomText() {
            Random random = new Random();
            Set<Integer> set = new HashSet<Integer>();
            while (set.size() < 4) {
                int randomInt = random.nextInt(10);
                set.add(randomInt);
            }
            StringBuffer sb = new StringBuffer();
            for (Integer i : set) {
                sb.append("" + i);
            }
    
            return sb.toString();
        }

    下面再来运行:

    自定义View进阶

    自定义View显示一张图片,下面包含图片的文本介绍,类似相片介绍什么的,不过不重要,主要是学习自定义View的用法么。

    还记得上一篇讲的4个步骤么:

    1、自定义View的属性
    2、在View的构造方法中获得我们自定义的属性
    [ 3、重写onMesure ]
    4、重写onDraw

    直接切入正题:

    1、在res/values/attr.xml

    复制代码
    <?xml version="1.0" encoding="utf-8"?>  
    <resources>  
      
        <attr name="titleText" format="string" />  
        <attr name="titleTextSize" format="dimension" />  
        <attr name="titleTextColor" format="color" />  
        <attr name="image" format="reference" />  
        <attr name="imageScaleType">  
            <enum name="fillXY" value="0" />  
            <enum name="center" value="1" />  
        </attr>  
      
        <declare-styleable name="CustomImageView">  
            <attr name="titleText" />  
            <attr name="titleTextSize" />  
            <attr name="titleTextColor" />  
            <attr name="image" />  
            <attr name="imageScaleType" />  
        </declare-styleable>  
      
    </resources>  
    复制代码

    2、在构造中获得我们的自定义属性:

    复制代码
    public CustomImageView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
    
            TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomImageView, defStyle, 0);
    
            int n = a.getIndexCount();
    
            for (int i = 0; i < n; i++) {
                int attr = a.getIndex(i);
    
                switch (attr) {
                case R.styleable.CustomImageView_image:
                    mImage = BitmapFactory.decodeResource(getResources(), a.getResourceId(attr, 0));
                    break;
                case R.styleable.CustomImageView_imageScaleType:
                    mImageScale = a.getInt(attr, 0);
                    break;
                case R.styleable.CustomImageView_titleText:
                    mTitle = a.getString(attr);
                    break;
                case R.styleable.CustomImageView_titleTextColor:
                    mTextColor = a.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.CustomImageView_titleTextSize:
                    mTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
                    break;
    
                }
            }
            a.recycle();
            rect = new Rect();
            mPaint = new Paint();
            mTextBound = new Rect();
            mPaint.setTextSize(mTextSize);
            // 计算了描绘字体需要的范围
            mPaint.getTextBounds(mTitle, 0, mTitle.length(), mTextBound);
        }
    复制代码

    3、重写onMeasure

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            /**
             * 设置宽度
             */
            int specMode = MeasureSpec.getMode(widthMeasureSpec);
            int specSize = MeasureSpec.getSize(widthMeasureSpec);
    
            if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate
            {
                Log.e("xxx", "EXACTLY");
                mWidth = specSize;
            } else {
                // 由图片决定的宽
                int desireByImg = getPaddingLeft() + getPaddingRight() + mImage.getWidth();
                // 由字体决定的宽
                int desireByTitle = getPaddingLeft() + getPaddingRight() + mTextBound.width();
    
                if (specMode == MeasureSpec.AT_MOST)// wrap_content
                {
                    int desire = Math.max(desireByImg, desireByTitle);
                    mWidth = Math.min(desire, specSize);
                    Log.e("xxx", "AT_MOST");
                }
            }
    
            /***
             * 设置高度
             */
    
            specMode = MeasureSpec.getMode(heightMeasureSpec);
            specSize = MeasureSpec.getSize(heightMeasureSpec);
            if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate
            {
                mHeight = specSize;
            } else {
                int desire = getPaddingTop() + getPaddingBottom() + mImage.getHeight() + mTextBound.height();
                if (specMode == MeasureSpec.AT_MOST)// wrap_content
                {
                    mHeight = Math.min(desire, specSize);
                }
            }
            setMeasuredDimension(mWidth, mHeight);
        }

    4、重写onDraw

    复制代码
        @Override
        protected void onDraw(Canvas canvas) {
            // super.onDraw(canvas);
            /**
             * 边框
             */
            mPaint.setStrokeWidth(4);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(Color.CYAN);
            canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
    
            rect.left = getPaddingLeft();
            rect.right = mWidth - getPaddingRight();
            rect.top = getPaddingTop();
            rect.bottom = mHeight - getPaddingBottom();
    
            mPaint.setColor(mTextColor);
            mPaint.setStyle(Style.FILL);
            /**
             * 当前设置的宽度小于字体需要的宽度,将字体改为xxx...
             */
            if (mTextBound.width() > mWidth) {
                TextPaint paint = new TextPaint(mPaint);
                String msg = TextUtils.ellipsize(mTitle, paint, (float) mWidth - getPaddingLeft() - getPaddingRight(), TextUtils.TruncateAt.END).toString();
                canvas.drawText(msg, getPaddingLeft(), mHeight - getPaddingBottom(), mPaint);
    
            } else {
                // 正常情况,将字体居中
                canvas.drawText(mTitle, mWidth / 2 - mTextBound.width() * 1.0f / 2, mHeight - getPaddingBottom(), mPaint);
            }
    
            // 取消使用掉的快
            rect.bottom -= mTextBound.height();
    
            if (mImageScale == IMAGE_SCALE_FITXY) {
                canvas.drawBitmap(mImage, null, rect, mPaint);
            } else {
                // 计算居中的矩形范围
                rect.left = mWidth / 2 - mImage.getWidth() / 2;
                rect.right = mWidth / 2 + mImage.getWidth() / 2;
                rect.top = (mHeight - mTextBound.height()) / 2 - mImage.getHeight() / 2;
                rect.bottom = (mHeight - mTextBound.height()) / 2 + mImage.getHeight() / 2;
    
                canvas.drawBitmap(mImage, null, rect, mPaint);
            }
    
        }
    复制代码

    代码,结合注释和第一篇View的使用,应该可以看懂,不明白的留言。下面我们引入我们的自定义View:

    复制代码
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        xmlns:zhy="http://schemas.android.com/apk/res/com.zhy.customview02"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        android:orientation="vertical" >  
      
        <com.zhy.customview02.view.CustomImageView  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_margin="10dp"  
            android:padding="10dp"  
            zhy:image="@drawable/ic_launcher"  
            zhy:imageScaleType="center"  
            zhy:titleText="hello andorid ! "  
            zhy:titleTextColor="#ff0000"  
            zhy:titleTextSize="30sp" />  
      
        <com.zhy.customview02.view.CustomImageView  
            android:layout_width="100dp"  
            android:layout_height="wrap_content"  
            android:layout_margin="10dp"  
            android:padding="10dp"  
            zhy:image="@drawable/ic_launcher"  
            zhy:imageScaleType="center"  
            zhy:titleText="helloworldwelcome"  
            zhy:titleTextColor="#00ff00"  
            zhy:titleTextSize="20sp" />  
      
        <com.zhy.customview02.view.CustomImageView  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_margin="10dp"  
            android:padding="10dp"  
            zhy:image="@drawable/lmj"  
            zhy:imageScaleType="center"  
            zhy:titleText="妹子~"  
            zhy:titleTextColor="#ff0000"  
            zhy:titleTextSize="12sp" />  
      
    </LinearLayout>  
    复制代码

    我特意让显示出现3中情况:

    1、字体的宽度大于图片,且View宽度设置为wrap_content

    2、View宽度设置为精确值,字体的长度大于此宽度

    3、图片的宽度大于字体,且View宽度设置为wrap_content

    看看显示效果:

    View自定义属性

    1、引言

    对于自定义属性,大家肯定都不陌生,遵循以下几步,就可以实现:

    1. 自定义一个CustomView(extends View )类
    2. 编写values/attrs.xml,在其中编写styleable和item等标签元素
    3. 在布局文件中CustomView使用自定义的属性(注意namespace)
    4. 在CustomView的构造方法中通过TypedArray获取

    ps:如果你对上述几个步骤不熟悉,建议先熟悉下,再继续~

    那么,我有几个问题:

    • 以上步骤是如何奏效的?
    • styleable 的含义是什么?可以不写嘛?我自定义属性,我声明属性就好了,为什么一定要写个styleable呢?
    • 如果系统中已经有了语义比较明确的属性,我可以直接使用嘛?
    • 构造方法中的有个参数叫做AttributeSet 
      (eg: MyTextView(Context context, AttributeSet attrs) )这个参数看名字就知道包含的是参数的数组,那么我能不能通过它去获取我的自定义属性呢?
    • TypedArray是什么鬼?从哪冒出来的,就要我去使用?

    恩,针对这几个问题,大家可以考虑下,如何回答呢?还是说:老子会背上述4个步骤就够了~~

    2、常见的例子

    接下来通过例子来回答上述问题,问题的回答顺序不定~~大家先看一个常见的例子,即上述几个步骤的代码化。

    • 自定义属性的声明文件
    复制代码
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="test">
            <attr name="text" format="string" />
            <attr name="testAttr" format="integer" />
        </declare-styleable>
    </resources>
    复制代码
    • 自定义View类
    复制代码
    package com.example.test;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.View;
    
    public class MyTextView extends View {
    
        private static final StringTAG=MyTextView.class.getSimpleName();
    
        public MyTextView(Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.test);
            String text = ta.getString(R.styleable.test_testAttr);
            int textAttr = ta.getInteger(R.styleable.test_text, -1);
            Log.e(TAG, "text = " + text + " , textAttr = " + textAttr);
            ta.recycle();
        }
    
    }
    复制代码
    • 布局文件中使用
    复制代码
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:zhy="http://schemas.android.com/apk/res/com.example.test"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <com.example.test.MyTextView
            android:layout_width="100dp"
            android:layout_height="200dp"
            zhy:testAttr="520"
            zhy:text="helloworld" />
    
    </RelativeLayout>
    复制代码

    ok,大家花3s扫一下,运行结果为:

     MyTextView: text = helloworld , textAttr = 520

    应该都不意外吧,注意下,我的styleable的name写的是test,所以说这里并不要求一定是自定义View的名字。

    3、AttributeSet与TypedArray

    下面考虑:

    构造方法中的有个参数叫做AttributeSet(eg: MyTextView(Context context, AttributeSet attrs) )这个参数看名字就知道包含的是参数的集合,那么我能不能通过它去获取我的自定义属性呢?

    首先AttributeSet中的确保存的是该View声明的所有的属性,并且外面的确可以通过它去获取(自定义的)属性,怎么做呢? 
    其实看下AttributeSet的方法就明白了,下面看代码。

    复制代码
    public MyTextView(Context context, AttributeSet attrs) {
       super(context, attrs);
       int count = attrs.getAttributeCount();
       for (int i = 0; i < count; i++) {
          String attrName = attrs.getAttributeName(i);
          String attrVal = attrs.getAttributeValue(i);
          Log.e(TAG, "attrName = " + attrName + " , attrVal = " +attrVal);
       }
       // ==>use typedarray ...
    }
    复制代码

    输出:

    MyTextView(4136): attrName = layout_width , attrVal = 100.0dip
    MyTextView(4136): attrName = layout_height , attrVal = 200.0dip
    MyTextView(4136): attrName = text , attrVal = helloworld
    MyTextView(4136): attrName = testAttr , attrVal = 520

    结合上面的布局文件,你发现了什么? 
    我擦,果然很神奇,真的获得所有的属性,恩,没错,通过AttributeSet可以获得布局文件中定义的所有属性的key和value(还有一些方法,自己去尝试),那么是不是说TypedArray这个鬼可以抛弃了呢?答案是:NO!

    现在关注下一个问题:

    TypedArray是什么鬼?从哪冒出来的,就要我去使用?

    我们简单修改下,布局文件中的MyTextView的属性。

    <com.example.test.MyTextView
        android:layout_width="@dimen/dp100"
        android:layout_height="@dimen/dp200"
        zhy:testAttr="520"
        zhy:text="@string/hello_world" />

    现在再次运行的结果是:

    MyTextView(4692): attrName = layout_width , attrVal = @2131165234
    MyTextView(4692): attrName = layout_height , attrVal = @2131165235
    MyTextView(4692): attrName = text , attrVal = @2131361809
    MyTextView(4692): attrName = testAttr , attrVal = 520
    >>use typedarray
    MyTextView(4692): text = Hello world! , textAttr = 520

    发现了什么?通过AttributeSet获取的值,如果是引用都变成了@+数字的字符串。你说,这玩意你能看懂么?那么你看看最后一行使用TypedArray获取的值,是不是瞬间明白了什么。

    TypedArray其实是用来简化我们的工作的,比如上例,如果布局中的属性的值是引用类型(比如:@dimen/dp100),如果使用AttributeSet去获得最终的像素值,那么需要第一步拿到id,第二步再去解析id。而TypedArray正是帮我们简化了这个过程。

    贴一下:如果通过AttributeSet获取最终的像素值的过程:

    int widthDimensionId =  attrs.getAttributeResourceValue(0, -1);
    Log.e(TAG, "layout_width= "+getResources().getDimension(widthDimensionId));

    ok,现在别人问你TypedArray存在的意义,你就可以告诉他了。

    4、declare-styleable

    我们已经解决了两个问题,接下来,我们看看布局文件,我们有一个属性叫做:zhy:text。 
    总所周知,系统提供了一个属性叫做:android:text,那么我觉得直接使用android:text更nice,这样的话,考虑问题:

    如果系统中已经有了语义比较明确的属性,我可以直接使用嘛?

    答案是可以的,怎么做呢? 
    直接在attrs.xml中使用android:text属性。

    <declare-styleable name="test">
         <attr name="android:text" />
         <attr name="testAttr" format="integer" />
    </declare-styleable>

    注意,这里我们是使用已经定义好的属性,不需要去添加format属性(注意声明和使用的区别,差别就是有没有format)。 
    然后在类中这么获取:ta.getString(R.styleable.test_android_text);布局文件中直接android:text="@string/hello_world"即可。

    这里提一下,系统中定义的属性,其实和我们自定义属性的方式类似,你可以在sdk/platforms/android-xx/data/res/values该目录下看到系统中定义的属性。然后你可以在系统提供的View(eg:TextView)的构造方法中发现TypedArray获取属性的代码(自己去看一下)。

    ok,接下来,我在想,既然declare-styleable这个标签的name都能随便写,这么随意的话,那么考虑问题:

    styleable 的含义是什么?可以不写嘛?我自定义属性,我声明属性就好了,为什么一定要写个styleable呢?

    其实的确是可以不写的,怎么做呢?

    • 首先删除declare-styleable的标签

    那么现在的attrs.xml为:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <attr name="testAttr" format="integer" />
    </resources>

    * MyTextView实现

    复制代码
    package com.example.test;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.View;
    
    public class MyTextView extends View {
    
        private static final String TAG = MyTextView.class.getSimpleName();
    
        private static final int[] mAttr = { android.R.attr.text, R.attr.testAttr };
        private static final int ATTR_ANDROID_TEXT = 0;
        private static final int ATTR_TESTATTR = 1;
    
        public MyTextView(Context context, AttributeSet attrs) {
            super(context, attrs);
    
            // ==>use typedarray
            TypedArray ta = context.obtainStyledAttributes(attrs, mAttr);
    
            String text = ta.getString(ATTR_ANDROID_TEXT);
            int textAttr = ta.getInteger(ATTR_TESTATTR, -1);
            //输出 text = Hello world! , textAttr = 520
            Log.e(TAG, "text = " + text + " , textAttr = " + textAttr);
    
            ta.recycle();
        }
    
    }
    复制代码

    貌似多了些代码,可以看到我们声明了一个int数组,数组中的元素就是我们想要获取的attr的id。并且我们根据元素的在数组中的位置,定义了一些整形的常量代表其下标,然后通过TypedArray进行获取。 
    可以看到,我们原本的:

    R.styleable.test => mAttr
    R.styleable.test_text => ATTR_ANDROID_TEXT(0)
    R.styleable.test_testAttr => ATTR_TESTATTR(1)

    那么其实呢?android在其内部也会这么做,按照传统的写法,它会在R.java生成如下代码:

    复制代码
    public static final class attr {
        public static final int testAttr=0x7f0100a9;
    }
    public static final class styleable {
         public static final int test_android_text = 0;
         public static final int test_testAttr = 1;
         public static final int[] test = {
                0x0101014f, 0x7f0100a9
         };
    }
    复制代码

    ok,根据上述你应该发现了什么。styleale的出现系统可以为我们完成很多常量(int[]数组,下标常量)等的编写,简化我们的开发工作(想想如果一堆属性,自己编写常量,你得写成什么样的代码)。那么大家肯定还知道declare-styleable的name属性,一般情况下写的都是我们自定义View的类名。主要为了直观的表达,该declare-styleable的属性,都是改View所用的。

    其实了解该原理是有用的,详见:Android 自定义控件 优雅实现元素间的分割线

    ok,现在5个问题,回答了4个,第一个问题:

    自定义属性的几个步骤是如何奏效的?

    恩,上述以及基本涵盖了这个问题的答案,大家自己总结,所以:略。

    总结下今天的博客。

    • attrs.xml里面的declare-styleable以及item,android会根据其在R.java中生成一些常量方便我们使用(aapt干的),本质上,我们可以不声明declare-styleable仅仅声明所需的属性即可。
    • 我们在View的构造方法中,可以通过AttributeSet去获得自定义属性的值,但是比较麻烦,而TypedArray可以很方便的便于我们去获取。
    • 我们在自定义View的时候,可以使用系统已经定义的属性。
  • 相关阅读:
    一张图,理解JAVA体系结构、运行机制、JVN运行机制、Java平台(初学)
    高效的CSS代码(2)
    高效的CSS代码(1)
    hibernate的报错信息a different object with the same identifier value was already associated with the session解决办法
    从tomcat下载文件的配置方法(很全呢)
    mysql中对于时间的处理,时间的滚动,求时间间隔,切换时区等等
    分享一个在js中判断数据是undefined,NaN,null,的技巧
    Java中Date类型如何向前向后滚动时间,( 附工具类)
    如何让tomcat服务器运行在80端口,并且无需输入项目名即可访问项目()
    前端的字符串时间如何自动转换为后端Java的Date属性,介绍springMVC中如何解决时间转换问题
  • 原文地址:https://www.cnblogs.com/chenxibobo/p/5358004.html
Copyright © 2011-2022 走看看