1. 程式人生 > >Android群英傳讀書筆記---自定義控制元件(-)

Android群英傳讀書筆記---自定義控制元件(-)

自定義控制元件

雖然我也寫過自定義控制元件,但是從沒有進行一個系統的總結,正好借這本書的內容,重新梳理一下,
通常情況下,常用的有三種方法:

  • 對現有控制元件進行擴充套件
  • 通過組合控制元件來實現新的控制元件
  • 重寫view來實現全新的控制元件(最難)

1. 對現有控制元件進行拓展

對原生控制元件的擴充套件,只需要重寫onDraw(Canvas canvas)即可

 private void initPaint(){
        paint1 = new Paint();
        paint1.setColor(getResources().getColor(android.R
.color.holo_blue_bright)); paint1.setStyle(Paint.Style.FILL); paint2 = new Paint(); paint2.setColor(getResources().getColor(android.R.color.holo_green_dark)); paint2.setStyle(Paint.Style.FILL); } @Override protected void onDraw(Canvas canvas) { //TODO 回撥父類方法前,對TextView來說繪製文字內容之前 //繪製一個外層矩形 canvas.drawRect
(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint1); //繪製一個內層矩形 canvas.drawRect(10, 10, getMeasuredWidth()-10, getMeasuredHeight()-10, paint2); canvas.save(); //繪製文字前平移10px canvas.translate(10, 0); super.onDraw(canvas); canvas.restore(); //TODO 回撥父類方法後,對TextView來說繪製文字內容之後 }

控制元件效果如下圖:

再來一個複雜點的效果(動態文字閃爍效果):

思路:通過設定一個不斷變化的LinearGradient,並使用帶有該屬性的Paint物件來繪製要顯示的文字。首先,在onSizeChanged(),中進行一些物件的初始化工作,根據view的寬設定一個LinearGradient漸變渲染器。

private int mViewWidth;
    private Paint paint;
    private LinearGradient linearGradient;
    private Matrix matrix;
    private int mTranslate;
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if(mViewWidth==0){
            mViewWidth = getMeasuredWidth();
            if(mViewWidth>0){
                paint = getPaint();
                linearGradient = new LinearGradient(0, 0, mViewWidth, 0, 
                        new int[]{Color.BLUE,0xffffffff,Color.GREEN}, new float[]{0,1,2}, Shader.TileMode.MIRROR);
                paint.setShader(linearGradient);
                matrix = new Matrix();
            }
        }
    }

其中最關鍵的是,使用getPaint()方法獲取繪製當前TextView的Paint,並給這個Paint設定一個LinearGradient,
最後,
在onDraw()方法中,通過矩陣的方式來不斷平移漸變效果

 @Override
    protected void onDraw(Canvas canvas) {
        //TODO 回撥父類方法前,對TextView來說繪製文字內容之前
        super.onDraw(canvas);
        Log.e("mess", "------onDraw----");
        if(matrix != null){
            mTranslate += mViewWidth/5;
            if(mTranslate>2*mViewWidth){
                mTranslate =-mViewWidth;
            }
            matrix.setTranslate(mTranslate, 0);
            linearGradient.setLocalMatrix(matrix);
            postInvalidateDelayed(100);
        }
        //TODO 回撥父類方法後,對TextView來說繪製文字內容之後
    }

下面看效果圖:

2. 建立複合控制元件

複合控制元件,最常見的其實就是我們的TitleBar了,一般就是一個left+title+right組合,我以前是做成一個xml檔案,然後各個需要的地方,用<\include>標籤引用,看了書上的例子,是把我這種xml的形式,做成了一個單獨控制元件,就學習學習這種寫法吧。

Begin

(1). values 目錄下新建attrs.xml 定義屬性

<?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="TitleBar">
            <!-- 定義title文字,大小,顏色 -->
            <attr name="title" format="string" />
            <attr name="titleTextSize" format="dimension"/>
            <attr name="titleTextColor" format="color" />
            <!-- 定義left 文字,大小,顏色,背景 -->
            <attr name="leftText" format="string" />
            <attr name="leftTextSize" format="dimension" />
            <attr name="leftTextColor" format="color" />
            <!-- 表示背景可以是顏色,也可以是引用 -->
            <attr name="leftBackGround" format="color|reference" />
            <!-- 定義right 文字,大小,顏色,背景 -->
            <attr name="rightText" format="string" />
            <attr name="rightTextSize" format="dimension"/>
            <attr name="rightTextColor" format="color" />
            <attr name="rightBackGround" format="color|reference" />
        </declare-styleable>
    </resources>

(2). 獲取自定義的屬性

  /**
     * 獲取自定義的屬性
     * 
     * @param context
     */
    private int leftTextColor;
    private Drawable leftBackGround;
    private String leftText;
    private float leftTextSize;

    private int rightTextColor;
    private String rightText;
    private float rightTextSize;

    private int titleTextColor;
    private String titleText;
    private float titleTextSize;

    private void initAttr(Context context, AttributeSet attrs) {
        TypedArray typed = context.obtainStyledAttributes(attrs, R.styleable.TitleBar);
        // 從TypedArray中取出對應的值為要設定的屬性賦值,給個預設值
        leftTextColor = typed.getColor(R.styleable.TitleBar_leftTextColor, 0XFFFFFFFF);
        leftBackGround = typed.getDrawable(R.styleable.TitleBar_leftBackGround);
        leftText = typed.getString(R.styleable.TitleBar_leftText);
        leftTextSize = typed.getDimension(R.styleable.TitleBar_leftTextSize, 20);

        rightTextColor = typed.getColor(R.styleable.TitleBar_rightTextColor, 0XFFFFFFFF);
        rightText = typed.getString(R.styleable.TitleBar_rightText);
        rightTextSize = typed.getDimension(R.styleable.TitleBar_rightTextSize, 20);

        titleTextColor = typed.getColor(R.styleable.TitleBar_titleTextColor, 0XFFFFFFFF);
        titleText = typed.getString(R.styleable.TitleBar_title);
        titleTextSize = typed.getDimension(R.styleable.TitleBar_titleTextSize, 20);
        // 不要忘記呼叫
        typed.recycle();

    }

(3).程式碼佈局元件

    private TextView titleView;
    private Button leftButton;
    private Button rightButton;

    private RelativeLayout.LayoutParams leftParams;
    private RelativeLayout.LayoutParams rightParams;
    private RelativeLayout.LayoutParams titleParams;

    /**
     * 程式碼佈局
     * 
     * @param context
     */
    @SuppressWarnings("deprecation")
    private void initView(Context context) {
        titleView = new TextView(context);
        leftButton = new Button(context);
        rightButton = new Button(context);

        // 為建立的元件賦值
        titleView.setText(titleText);
        titleView.setTextSize(titleTextSize);
        titleView.setTextColor(titleTextColor);
        titleView.setGravity(Gravity.CENTER);

        leftButton.setText(leftText);
        leftButton.setTextColor(leftTextColor);
        leftButton.setBackgroundDrawable(leftBackGround);
        leftButton.setTextSize(leftTextSize);

        rightButton.setText(rightText);
        rightButton.setTextSize(rightTextSize);
        rightButton.setTextColor(rightTextColor);

        // 為元件佈局
        // 在左邊
        leftParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
        leftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
        addView(leftButton, leftParams);

        // 在右邊
        rightParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
        rightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
        addView(rightButton, rightParams);

        //中間
        titleParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
        rightParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
        addView(titleView, titleParams);

        //新增點選監聽,(下面講述如何引入的)
        leftButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                if (listener != null) {
                    listener.leftClick();
                }
            }
        });

        rightButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                if (listener != null) {
                    listener.rightClick();
                }
            }
        });

    }

(4).定義介面

public interface TitleBarClickListener{
        //左點選
        void leftClick();
        //右點選
        void rightClick();
    }

(5).暴露介面給呼叫者

public void setTitleBarClickListener(TitleBarClickListener listener) {
        this.listener = listener;
    }

看下例子 佈局檔案

<com.example.day_1.TitleBar
        android:id="@+id/titlebar"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_alignParentBottom="true"
        app:leftBackGround="#ff000000"
        app:leftText="left"
        app:leftTextColor="#ffff6734"
        app:leftTextSize="25dp" 
        app:rightText="right"
        app:rightTextSize="25dp"
        app:rightTextColor="#ff123456"
        app:title="title"
        app:titleTextColor="#ff654321"/>

程式碼

private TitleBar titlebar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        titlebar = (TitleBar) findViewById(R.id.titlebar);
        titlebar.setTitleBarClickListener(this);
    }

    @Override
    public void leftClick() {
        Toast.makeText(this, "left---", Toast.LENGTH_LONG).show();

    }

    @Override
    public void rightClick() {
        Toast.makeText(this, "right---", Toast.LENGTH_LONG).show();

    }

效果如下圖:

3.重寫view實現全新的控制元件

這個是我覺著三種自定義型別裡,最難得了,迄今為止沒有寫出過什麼出彩的控制元件,所以還是好好學習書上的內容吧,增加新技能。

———————————Begin——————————-

(1) .弧線展示圖
這裡寫圖片描述

思路:這個view可以分為三個部分,中間的圓圈,中間顯示的文字,外圈的圓弧。只要有了這樣的思路,剩餘的就是在onDraw()方法中去繪製了。

  • 測量控制元件大小,onMeasure()
private int mMeasureHeigth;// 控制元件高度
        private int mMeasureWidth;// 控制元件寬度
        // 圓形
        private Paint mCirclePaint;
        private float mCircleXY;//圓心座標
        private float mRadius;//圓形半徑
        // 圓弧
        private Paint mArcPaint;
        private RectF mArcRectF;//圓弧的外切矩形
        private float mSweepAngle;//圓弧的角度
        private float mSweepValue = 50;
        // 文字
        private Paint mTextPaint;
        private String mShowText;//文字內容
        private float mShowTextSize;//文字大小

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            mMeasureWidth = MeasureSpec.getSize(widthMeasureSpec);//獲取控制元件寬度
            mMeasureHeigth = MeasureSpec.getSize(heightMeasureSpec);//獲取控制元件高度
            setMeasuredDimension(mMeasureWidth, mMeasureHeigth);
            initView();
        }

        /**
         *準備畫筆,
         */
        private void initView() {

            float length = Math.min(mMeasureWidth,mMeasureHeigth)
            // 圓
            mCircleXY = length / 2;// 確定圓心座標
            mRadius = (float) (length * 0.5 / 2);// 確定半徑
            mCirclePaint = new Paint();
            mCirclePaint.setAntiAlias(true);// 去鋸齒
            mCirclePaint.setColor(getResources().getColor(android.R.color.holo_green_dark));

            // 弧線
            // 矩形
            mArcRectF = new RectF((float) (length * 0.1), (float) (length * 0.1), (float) (length * 0.9),
                (float) (length * 0.9));
            mSweepAngle = (mSweepValue / 100f) * 360f;
            mArcPaint = new Paint();
            mArcPaint.setColor(getResources().getColor(android.R.color.holo_blue_bright));
            mArcPaint.setStrokeWidth((float) (length * 0.1));//圓弧寬度
            mArcPaint.setStyle(Style.STROKE);//圓弧
            // 文字
            mShowText = setShowText();
            mShowTextSize = setShowTextSize();
            mTextPaint = new Paint();
            mTextPaint.setTextSize(mShowTextSize);
            mTextPaint.setTextAlign(Paint.Align.CENTER);

        }

        private float setShowTextSize() {
            this.invalidate();
            return 50;
        }

        private String setShowText() {
            this.invalidate();
            return "Android Skill";
        }

        public void forceInvalidate() {
            this.invalidate();
        }

        public void setSweepValue(float sweepValue) {
            if (sweepValue != 0) {
                mSweepValue = sweepValue;
            } else {
                mSweepValue = 25;
            }
            this.invalidate();
        }
  • 繪製控制元件, onDraw()
@Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            // 繪製圓
            canvas.drawCircle(mCircleXY, mCircleXY, mRadius, mCirclePaint);
            // 繪製圓弧,逆時針繪製,角度跟
            canvas.drawArc(mArcRectF, 90, mSweepAngle, false, mArcPaint);
            // 繪製文字
            canvas.drawText(mShowText, 0, mShowText.length(), mCircleXY, mCircleXY + mShowTextSize / 4, mTextPaint);

        }
圓弧的繪製,開始位置如下圖所示角度,以順時針繪製圖形

  • 效果如下圖

(2).音訊條形圖

思路:繪製n個小矩形,每個矩形有些偏移即可

準備工作

private int mWidth;//控制元件的寬度
        private int mRectWidth;// 矩形的寬度
        private int mRectHeight;// 矩形的高度
        private Paint paint;
        private int mRectCount;// 矩形的個數
        private int offset = 5;// 偏移
        private double mRandom;
        private LinearGradient lg;// 漸變

        private void initView() {
            paint = new Paint();
            paint.setColor(Color.GREEN);
            paint.setStyle(Paint.Style.FILL);
            mRectCount = 12;
        }

        //重寫onSizeChanged方法

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            mWidth = getWidth();
            mRectHeight = getHeight();
            mRectWidth = (int) (mWidth * 0.6 / mRectCount);
            lg = new LinearGradient(0, 0, mRectWidth, mRectHeight, Color.GREEN, Color.BLUE, TileMode.CLAMP);
            paint.setShader(lg);
        }

        //重寫onDraw方法
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            for (int i = 0; i < mRectCount; i++) {
                mRandom = Math.random();
                float currentHeight = (int) (mRectHeight * mRandom);
                canvas.drawRect((float) (mWidth * 0.4 / 2 + mRectWidth * i + offset * i), currentHeight,
                    (float) (mWidth * 0.4 / 2 + mRectWidth * (i + 1) + offset * i), mRectHeight, paint);
            }
            postInvalidateDelayed(1000);
        }

view的繪製座標如下圖所示:

效果圖如下:

後記:

就像本書作者說的:無論多麼複雜的自定義view都是慢慢迭代起來的功能,不要被自定義view嚇到,總之,非常感謝作者,我對自定義view總算是有了一個完整的認識