Androd自定義控件(二)自定義類繼承view

在自定義控件(一)中呢,大家已經對自定義控件有了一個基本的認識,今天就和大家分享一下如何自定義類繼承view來實現我們的功能。

需求

這裏寫圖片描述
效果圖如上圖所示,要求如下:

  1. 背景顏色從上到下由深變淺。
  2. 小黃點的數量從上到下由多到少。
  3. 小黃點的顏色大小隨機。
  4. 該控件可分爲5個等級,最佳爲背景全白,沒有小黃點。最嚴重爲顏色最深,小黃點最多。

好,需求就是這個樣子。看到這裏呢,希望小夥伴們先不要着急往下看,可以設身處地的想一想,如果是自己做的話,要怎樣實現。

實現方案

接下來跟大家分享一下我的實現方案。看到這個需求我想到的方案有兩個。

  1. 把每一顆牙齒作爲一個單位,我們去控制單個牙齒的背景色和小黃點的數量。然後繪製出四顆牙齒,按順序擺放。優點是可以比較精確的控制每顆牙齒的顯示情況。缺點是每顆牙齒都要去繪製,需要自己去用畫筆滑出牙齒的輪廓,背景和小黃點。
  2. 把四顆牙齒作爲一個單位,分成上下兩層。上層是一張中間透明,兩邊是白色的圖片。下層是一個從上到下顏色由深到錢,小黃點由少到多的自定義view。通過繪製不同的背景層,顯示我們要的效果。優點是隻需要繪製背景即可實現我們要的功能,不需要去分別繪製每顆牙齒的輪廓。鑑於我們的需求,不需要精確到每顆牙齒,這裏我們選擇第二種實現方案。

具體實現

我們先梳理一下第二種實現方式需要用到的知識點

1.自定義類繼承view實現自定義控件的步驟。
2.如何去測量控件的大小。
3.畫筆的使用。

好,接下來我們來按步驟實現這個控件。

初始化

/**
     * 定義兩種模式,平均和漸變
     */
    public enum MODE {
        AVERAGE, SHADE
    }

    public Chart(Context context) {
        super(context);
        init();
    }

    public Chart(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public Chart(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        random = new Random();

        isNull = false;
        isBig = false;
        deltaX = 20;
        deltaY = 30;
        bgAlpha = 50;
        sizeY = 1;
        floatY = 10;
        de = 3;
        mode = MODE.AVERAGE;
    }

爲了讓小黃點從上到下由少到多的顯示,我是這樣實現的,隨機生成一個0-20的初始Y軸座標,然後繪製每列的小黃點。小黃點的間距是逐漸減小的,這樣小黃點就會越來越密集。而X的座標是一個初始值加上一個隨機值,這樣繪製出來的小黃點每兩列間距也是隨機的。顯示效果會比較好看。
在初始化的方法裏,我們初始化了一些x軸和y軸的初始數據。同時我定義了兩種模式,一種是小黃點均勻分佈,兩一種是逐漸增多。

測量

系統幫我們測量的高度和寬度都是MATCH_PARNET,當我們設置明確的寬度和高度時,系統幫我們測量的結果就是我們設置的結果,當我們設置爲WRAP_CONTENT,或者MATCH_PARENT系統幫我們測量的結果就是MATCH_PARENT的長度。
所以,當設置了WRAP_CONTENT時,我們需要自己進行測量,即重寫onMesure方法”:

重寫之前先了解MeasureSpec的specMode,一共三種類型:

EXACTLY:一般是設置了明確的值或者是MATCH_PARENT

AT_MOST:表示子佈局限制在一個最大值內,一般爲WARP_CONTENT

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

下面是測量的方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getMeasuredLength(widthMeasureSpec, true, this), getMeasuredLength(heightMeasureSpec, false, this));
    }

    /**
     * 根據佈局模式計算寬高
     *
     * @param measureSpec
     * @param isWidth
     * @return
     */
    public static int getMeasuredLength(int measureSpec, boolean isWidth, View view) {
        int result;
        int specMode = View.MeasureSpec.getMode(measureSpec);
        int specSize = View.MeasureSpec.getSize(measureSpec);

        if (specMode == View.MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            if (isWidth) {
                result = view.getPaddingLeft() + view.getPaddingRight();
            } else {
                result = view.getPaddingTop() + view.getPaddingBottom();
            }
            if (specMode == View.MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

繪製

@Override
    protected void onDraw(Canvas canvas) {
        //isNull爲true,背景爲純白色,不顯示牙菌斑
        if (isNull) {
            //填充背景顏色
            mPaint.setColor(0xffffffff);
            canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
        } else {
            //填充背景顏色
            mPaint.setColor(0xffFEF8ED);
            //繪製點
//        int canvasWidth = canvas.getWidth();
            int canvasHeight = canvas.getHeight();
            LinearGradient lg = new LinearGradient(0, 0, 0, canvasHeight, 0x7ff1b351, 0xfff1b351, Shader.TileMode.MIRROR);
            mPaint.setShader(lg);
            mPaint.setAlpha(bgAlpha);
            canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
            int x = 0;
            //繪製Cap爲ROUND的點
            mPaint.setStrokeCap(Paint.Cap.ROUND);
            switch (mode) {
                case AVERAGE:
                    for (int i = 0; i < 80; i++) {
                        int startX = deltaX + random.nextInt(10);//x每次增加的距離
                        int startY = 0 + random.nextInt(floatY);
                        x = x + startX;
                        int y = startY;
                        for (int j = 0; j < 80; j++) {
                            int alpha = 70 + random.nextInt(100);//隨機生成透明度的點
                            int size = 5 + random.nextInt(5);//設置線寬,如果不設置線寬,無法繪製點
                            mPaint.setStrokeWidth(size);
                            mPaint.setAlpha(alpha);
                            mPaint.setShader(null);//清空漸變色
                            canvas.drawPoint(x, y + deltaY, mPaint);
                            y = y + deltaY;
                        }
                    }
                    break;
                case SHADE:
                    for (int i = 0; i < 80; i++) {
                        int startX = deltaX + random.nextInt(10);//x每次增加的距離
                        int startY = 0 + random.nextInt(floatY);
                        x = x + startX;
                        int deltay = deltaY;
                        int y = startY;
                        boolean isFirst = true;
//                    Log.e(TAG, "" + startY);
                        for (int j = 0; j < 80; j++) {
                            int alpha = 70 + random.nextInt(100);//隨機生成透明度的點
                            int size;
                            if (isBig) {
                                size = (int) ((5 + random.nextInt(10)) * de / 3);
                            } else {
                                size = (int) ((3 + random.nextInt(7)) * de / 3);//設置線寬,如果不設置線寬,無法繪製點
                            }
                            mPaint.setStrokeWidth(size);
                            mPaint.setAlpha(alpha);
                            if (deltay < 15) {
                                deltay = 20;
                            }
                            if (isFirst) {
                                canvas.drawPoint(x, y, mPaint);
                                isFirst = false;
                            } else {
                                canvas.drawPoint(x, y + deltay, mPaint);
                                y = y + deltay;
                            }
                            deltay -= sizeY;
                        }
                    }
                    break;
            }
        }
    }

在這裏,我們給先給畫筆設置一個線性隨機色,繪製背景。
然後設置設置畫筆粗細爲隨機,透明度隨機,來繪製小黃點。爲了使小黃點在不同分辨率上顯示出相同的效果,它的像素值我們來根據手機的分辨率去計算。

public int getDis(Activity activity) {
        DisplayMetrics metric = new DisplayMetrics();
        activity.getWindowManager().getDefaultDisplay().getMetrics(metric);
        int width = metric.widthPixels; // 屏幕寬度(像素)
        int height = metric.heightPixels; // 屏幕高度(像素)
        float density = metric.density; // 屏幕密度(0.75 / 1.0 / 1.5 / 2.0)
        int densityDpi = metric.densityDpi; // 屏幕密度DPI(120 / 160 / 240 / 320)
        if (density < 1.0) {
            return -1;
        } else if (density == 1.0) {
            return 0;
        } else if (density == 1.5) {
            return 1;
        } else {
            return 2;
        }
    }

引用

到這裏這個控件基本上就寫完了,然後我們需要給外部提供一個方法,去設置這個控件的不同狀態。

/**
     * 根據不同的時間,顯示不同的背景(小圖)
     *
     * @param time
     */
    public void setChartLittele(int time, float de) {
        this.de = de;
        if (time >= 400) {
            setBg(true, 0, 0, 0, 0, 0, Chart.MODE.SHADE);
        } else if (time >= 320 && time < 400) {
            setBg(false, 26, 36, 1, 20, 10, Chart.MODE.SHADE);
        } else if (time >= 240 && time < 320) {
            setBg(false, 22, 32, 1, 20, 20, Chart.MODE.SHADE);
        } else if (time >= 160 && time < 240) {
            setBg(false, 18, 28, 1, 20, 30, Chart.MODE.SHADE);
        } else if (time >= 80 && time < 160) {
            setBg(false, 14, 24, 1, 20, 40, Chart.MODE.SHADE);
        } else if (time >= 0 && time < 80) {
            setBg(false, 10, 20, 1, 20, 50, Chart.MODE.SHADE);
        }
    }
/**
     * 設置背景牙菌斑的密集程度
     *
     * @param deltaX  x軸每次往後移動的距離(隨機)
     * @param deltaY  y軸每次往下移動的距離
     * @param sizeY   漸變模式y每次減少的距離
     * @param floatY  Y軸開始的隨機座標
     * @param bgAlpha 背景漸變色的透明度
     * @param mode    模式
     */
    public void setBg(boolean isNull, int deltaX, int deltaY, int sizeY, int floatY, int bgAlpha, MODE mode) {
        this.isNull = isNull;
        this.deltaX = deltaX;
        this.deltaY = deltaY;
        this.sizeY = sizeY;
        this.floatY = floatY;
        this.bgAlpha = bgAlpha;
        this.mode = mode;
        invalidate();
    }

看一下佈局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#fff"
    android:gravity="center" >

    <FrameLayout
        android:layout_width="115dp"
        android:layout_height="115dp" >

        <com.oracleen.view.Chart
            android:id="@+id/chart"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/check_right_second" />
    </FrameLayout>

</LinearLayout>

這裏我們用了一個framelayout,上層是一張鏤空的圖片,下層是我們繪製的背景。
然後在activity中設置背景的顯示級別。

chart = (Chart) findViewById(R.id.chart);
chart.setChartLittele(300, getDis(this));

OK,搞定。demo鏈接。
demo

下一章我會和大家分享一下組合view的實現,把今天實現的view和前景圖片做成一個控件,添加一些屬性,使用起來會更方便一些。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章