自定义TextView-自定义View、自定义属性基础案例

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qiantanlong/article/details/82867148

       自定义view是安卓开发的基础,本身没有太多的难点,需要考虑的知识点也不多,主要有canvas绘制相关知识、view控件的测量、自定义属性等,本案例通过一个展示文本的自定义控件,努力演示上述知识点,为自定义view打好基础。读懂这个案例需要你对自定义view有一定的基础,如果你对自定义view一无所知,建议自行查阅相关知识后再看这个案例,因为案例中没有详细的步骤介绍,只对核心的知识点做了说明和注释。

自定义的TextView类:

核心知识点已经注释,希望可以对你读懂代码有所帮助。

package hongzhen.com.defineviewdemo.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import hongzhen.com.defineviewdemo.R;

/**
 * 自定义TextView控件,用于展示文本
 * 实现自定义属性 src-文本内容 text_color-文本颜色 text_size-文本大小 bg_width-控件宽度 bg_height-控件高度
 * 考虑padding属性
 * 考虑style的设置
 */
public class TextView extends View {
    private static final String TAG = "TextView";
    private Paint paint;
    private String mText = "";
    private String textColor;
    private float mHeight;
    private float textSize;
    private float mWidth;
    private int paddingLeft;
    private int paddingTop;
    private int paddingRight;
    private int paddingBottom;

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

    public TextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FirstView,
                defStyleAttr, R.style.myDefaultStyle);
        mText = typedArray.getString(R.styleable.FirstView_src);
        if (mText == null) {
            mText = "";
        }
        textColor = typedArray.getString(R.styleable.FirstView_text_color);
        mWidth = typedArray.getDimension(R.styleable.FirstView_bg_width, 100);
        mHeight = typedArray.getDimension(R.styleable.FirstView_bg_height, 50);
        textSize = typedArray.getDimension(R.styleable.FirstView_text_size, 50);
        Log.i(TAG, "textColor-" + textColor);
        Log.i(TAG, "textSize-" + textSize);
        Log.i(TAG, "mWidth-" + mWidth);
        Log.i(TAG, "mHeight-" + mHeight);
        typedArray.recycle();
        //考虑自定义view设置的padding属性,由于layout_margin相关属性是viewgroup负责的,因此只有自定义
        //viewgroup时才需要考虑其子view的layout_margin属性生效,view无需考虑layout_margin属性
        paddingLeft = getPaddingLeft();
        paddingTop = getPaddingTop();
        paddingRight = getPaddingRight();
        paddingBottom = getPaddingBottom();
        init();
    }

    /**
     * 该方法defStyleRes参数需要API-21以上才支持,因此不建议重写该方法和在该方法进行初始化操作
     *
     * @param context
     * @param attrs
     * @param defStyleAttr
     * @param defStyleRes
     */
    public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    /**
     * 初始化画笔参数
     */
    private void init() {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        //设置画笔大小,也就是文本的大小
        paint.setTextSize(textSize);
        //设置画笔的颜色,也就是文本的颜色
        paint.setColor(getColorFromXML(textColor));
    }

    @Override
    protected void onDraw(Canvas canvas) {
       /* Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.GRAY);
        paint.setTextSize(100);
        setLayerType(View.LAYER_TYPE_SOFTWARE, paint);
        paint.setShadowLayer(10, 6, 6, Color.RED);
        canvas.drawText("Android Studio", 20, 120, paint);*/
        //将文字放在正中间
        Rect textRect = this.getTextRect();
        int viewWidth = getMeasuredWidth();
        int viewHeight = getMeasuredHeight();

        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        int x = (viewWidth - textRect.width()) / 2;
        int y = (int) (viewHeight / 2 +
                (fontMetrics.descent - fontMetrics.ascent) / 2
                - fontMetrics.descent);
        //x,y坐标点,大概是文本左下角的位置
        canvas.drawText(mText, x, y, paint);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取根据文本生成的宽、高,进而确定控件需要的宽高
        Rect rect = getTextRect();
        int textWidth = rect.width();
        int textHeight = rect.height();
        int width = measureWidth(widthMeasureSpec, textWidth);
        int height = measureHeight(heightMeasureSpec, textHeight);
        setMeasuredDimension(width, height);
    }

    /**
     * 获取文字所占的尺寸,矩形区域
     *
     * @return
     */
    private Rect getTextRect() {
        //根据 Paint 设置的绘制参数计算文字所占的宽度
        Rect rect = new Rect();
        //文字所占的区域大小保存在 rect 中
        if (mText != null) {
            paint.getTextBounds(mText, 0, mText.length(), rect);
        }
        return rect;
    }

    /**
     * 测量组件宽度
     *
     * @param widthMeasureSpec
     * @param textWidth        文字所占宽度
     * @return
     */
    private int measureWidth(int widthMeasureSpec, int textWidth) {
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        int width = 0;

        if (mode == MeasureSpec.EXACTLY) {
            //宽度为 match_parent 和具体值时,直接将 size 作为组件的宽度
            width = size;
        } else if (mode == MeasureSpec.AT_MOST) {
            //宽度为 wrap_content,宽度需要计算, 此处为文字宽度
            if (textWidth > mWidth) {
                //设置的宽度小于文本宽度,则取文本宽度
                //考虑padding的值
                width = textWidth + paddingLeft + paddingRight;
            } else {
                width = (int) mWidth;
            }
        }
        return width;
    }

    /**
     * 测量组件高度
     *
     * @param heightMeasureSpec
     * @param textHeight        文字所占高度
     * @return
     */
    private int measureHeight(int heightMeasureSpec, int textHeight) {
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        int size = MeasureSpec.getSize(heightMeasureSpec);
        int height = 0;
        if (mode == MeasureSpec.EXACTLY) {
            //宽度为 match_parent 和具体值时,直接将 size 作为组件的高度
            height = size;
        } else if (mode == MeasureSpec.AT_MOST) {
            //高度为 wrap_content,高度需要计算, 此处为文字高度
            //考虑padding的值
            height = textHeight + paddingTop + paddingBottom;
        }
        return height;
    }

    /**
     * 将xml页面设置的颜色字符串,转换为int的颜色,#fff;#ffff;#ffffff;#ffffffff.四种格式转换为int颜色
     *
     * @param src
     * @return
     */
    private int getColorFromXML(String src) {
        if (TextUtils.isEmpty(src)) {
            return Color.BLACK;
        } else {
            if (src.contains("#")) {
                src = src.replace("#", "");
                StringBuffer result = new StringBuffer();
                if (src.length() == 3) {
                    char[] chars = src.toCharArray();
                    result.append("#");
                    result.append("ff");
                    for (int i = 0; i < chars.length; i++) {
                        result.append(chars[i]);
                        result.append(chars[i]);
                    }
                    return Color.parseColor(result.toString());
                } else if (src.length() == 4) {
                    char[] chars = src.toCharArray();
                    result.append("#");
                    for (int i = 0; i < chars.length; i++) {
                        result.append(chars[i]);
                        result.append(chars[i]);
                    }
                    return Color.parseColor(result.toString());
                } else if (src.length() == 6) {
                    result.append("#");
                    result.append("ff");
                    result.append(src);
                    return Color.parseColor(result.toString());
                } else if (src.length() == 8) {
                    result.append("#");
                    result.append(src);
                    return Color.parseColor(result.toString());
                } else {
                    return Color.BLACK;
                }
            } else {
                return Color.BLACK;
            }
        }
    }
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".TextViewActivity">

    <hongzhen.com.defineviewdemo.view.TextView
        style="@style/myStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#aaa"
        app:text_size="30sp"
        app:src="Hello"/>

    <hongzhen.com.defineviewdemo.view.TextView
        style="@style/myStyle"
        android:layout_width="150dp"
        android:layout_height="60dp"
        android:background="#aaa"
        app:src="World"/>

    <hongzhen.com.defineviewdemo.view.TextView
        style="@style/myStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#aaa"
        app:src="可以"/>
</LinearLayout>

attrs.xml文件代码:

<declare-styleable name="FirstView">
        <attr name="src" format="string"/>
        <attr name="text_color" format="string"></attr>
        <attr name="text_size" format="dimension"></attr>
        <attr name="bg_width" format="dimension"></attr>
        <attr name="bg_height" format="dimension"></attr>
    </declare-styleable>

style.xml文件代码:

<style name="myStyle">
        <item name="bg_width">36dp</item>
        <item name="text_color">#f00</item>
        <item name="android:layout_margin">5dp</item>
        <item name="android:padding">5dp</item>
    </style>

猜你喜欢

转载自blog.csdn.net/qiantanlong/article/details/82867148