自定義View實例(四)----自定義條形柱狀圖

本來是想寫一篇點贊效果的自定義View實例的,後來好基友因爲項目緊急叫我幫他擼一個條形柱狀圖,大致瞄了一眼原型圖以後便開擼了。最後做出來的效果與原型圖八九不離十,看一下最後實現的效果圖:

這裏寫圖片描述

一個普通的條形柱狀圖,統計的是12個月份兩種狀態的數值,選中的長條背景顏色會加深,並且顯示當前兩種狀態的數值。看看怎麼實現的吧:

一.準備工作:

1.數據準備:

       myChartView = (MyChartView) findViewById(R.id.my_chart_view);
        relativeLayout = (RelativeLayout) findViewById(R.id.linearLayout);
        Random random = new Random();
        while (list.size() < 24) {
            int randomInt = random.nextInt(100);
            list.add(randomInt);
        }
        myChartView.setList(list);

這裏生成了24個100以內的隨機整數,所以第二次進入的時候,柱狀圖的高度數值會有所改變。

2.設置屬性,計算寬高,進行必要的初始化:

    public MyChartView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //獲取我們自定義的樣式屬性
        TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyChartView, defStyleAttr, 0);
        int n = array.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = array.getIndex(i);
            switch (attr) {
                case R.styleable.MyChartView_leftColor:
                    // 默認顏色設置爲黑色
                    leftColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView_rightColor:
                    rightColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView_xyColor:
                    lineColor = array.getColor(attr, Color.BLACK);
                    break;

            }
        }
        array.recycle();
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mBound = new Rect();
        mChartPaint = new Paint();
        mChartPaint.setAntiAlias(true);
        mShadowPaint = new Paint();
        mShadowPaint.setAntiAlias(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width;
        int height;
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = widthSize * 1 / 2;
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = heightSize * 1 / 2;
        }

        setMeasuredDimension(width, height);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mWidth = getWidth();
        mHeight = getHeight();
        mStartWidth = getWidth() / 13;
        mSize = getWidth() / 39;
        mChartWidth = getWidth() / 13 - mSize;
    }

相信看過前幾篇博客的小夥伴,對這些代碼以及非常熟悉了,這也是自定義View前必不可少的準備工作。

二.繪製柱狀圖:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mPaint.setColor(lineColor);
        //畫座標軸
        canvas.drawLine(0, mHeight - 100, mWidth, mHeight - 100, mPaint);
        for (int i = 0; i < 12; i++) {
            //畫刻度線
            canvas.drawLine(mStartWidth, mHeight - 100, mStartWidth, mHeight - 80, mPaint);
            //畫數字
            mPaint.setTextSize(30);
            mPaint.setTextAlign(Paint.Align.CENTER);
            mPaint.getTextBounds(String.valueOf(i + 1) + "月", 0, String.valueOf(i).length(), mBound);
            canvas.drawText(String.valueOf(i + 1) + "月", mStartWidth - mBound.width() * 1 / 2, mHeight - 60 + mBound.height() * 1 / 2, mPaint);
            mStartWidth += getWidth() / 13;


        }
        //畫柱狀圖
        for (int i = 0; i < list.size(); i++) {
            int size = mHeight / 150;
            //偶數
            if (i % 2 == 0) {
                mChartPaint.setColor(leftColor);
            } else {
                mChartPaint.setColor(rightColor);
            }
            mChartPaint.setStyle(Paint.Style.FILL);
            //畫陰影
            if (i == number * 2 || i == number * 2 + 1) {
                mShadowPaint.setColor(Color.LTGRAY);

            } else {
                mShadowPaint.setColor(Color.WHITE);
            }
            canvas.drawRect(mChartWidth, 0, mChartWidth + mSize, mHeight - 100, mShadowPaint);
            //畫柱狀圖
            canvas.drawRect(mChartWidth, mHeight - 100 - list.get(i) * size, mChartWidth + mSize, mHeight - 100, mChartPaint);// 長方形
            mChartWidth += (i % 2 == 0) ? (getWidth() / 39) : (getWidth() / 13 - mSize);

        }


    }

從上到下,循序漸進,這裏分享一個小技巧:自定義View之前先在本子上把大致位置畫出來,座標在紙上算一遍,這樣既不容易出錯,也對自己寫代碼有幫助。實現不是很複雜,代碼註釋也比較清楚,就不詳細分析了。

三.點擊事件的實現:

示例圖中可以看到,點擊柱狀圖的不同位置,會加深點擊區域的背景,並且顯示當前數值,看看怎麼實現的:

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        int x = (int) ev.getX();
        int y = (int) ev.getY();
        int left = 0;
        int top = 0;
        int right = mWidth / 12;
        int bottom = mHeight - 100;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                for (int i = 0; i < 12; i++) {
                    rect = new Rect(left, top, right, bottom);
                    left += mWidth / 12;
                    right += mWidth / 12;
                    if (rect.contains(x, y)) {
                        listener.getNumber(i, x, y);
                        number = i;
                        invalidate();
                    }
                }
                break;
        }
        return true;

    }

    public interface getNumberListener {
        void getNumber(int number, int x, int y);
    }

通過計算得到點擊的區域代表的值,調用invalidate()方法進行重新繪製,加深背景顏色。
示例圖中顯示的數值會以一個小方塊的形式顯示出來,其實是通過接口回調得到點擊區域代表的值,以及點擊的x,y座標。然後怎樣在Activity中使用呢:

        myChartView.setListener(new MyChartView.getNumberListener() {
            @Override
            public void getNumber(int number, int x, int y) {
                relativeLayout.removeView(showText);
                RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
                        RelativeLayout.LayoutParams.WRAP_CONTENT);
                params.leftMargin = x - 100;
                if (x - 100 < 0) {
                    params.leftMargin = 0;
                } else if (x - 100 > relativeLayout.getWidth() - showText.getWidth()) {
                    params.leftMargin = relativeLayout.getWidth() - showText.getWidth();
                }
                params.topMargin = 100;
                showText.setLayoutParams(params);
                showText.setTextColor(getResources().getColor(R.color.white));
                showText.setTextSize(10);
                showText.setText("選擇的數字爲:" + list.get(number * 2) + "," + list.get(number * 2 + 1));
                showText.setBackground(getResources().getDrawable(R.drawable.flag_01));
                relativeLayout.addView(showText);

            }
        });

實現接口,每次將這個顯示的小方塊動態添加到佈局中去,顯示點擊的數值,加上x座標左右的限制,防止出界。

四.注意事項:

之前幾篇自定義View,有小夥伴和我反應,把應用切換至手機後臺時,再打開應用,發現自定義的View會消失,這是因爲Activity的生命週期影響了自定義View的生命週期。

可參考我之前一篇博客:Android自定義View探索(一)—生命週期

解決的辦法有很多,推薦兩個常用的:

1.在Activity中重寫onResume()方法,設置自定義View需要的初始值

2.在自定義View方法中重寫onWindowVisibilityChanged()方法,設置初始值

這篇博客我採用的是第二種方法:

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        if (visibility == VISIBLE) {
            mSize = getWidth() / 39;
            mStartWidth = getWidth() / 13;
            mChartWidth = getWidth() / 13 - mSize - 3;
        }
    }

這樣只要你不退出當前界面,將應用切換至後臺,過一段時間再次進入時,自定義View還是會保持最初的樣子,不會消失的。

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