Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android_自定義簽到View

Android_自定義簽到View

編輯:關於Android編程

一個類似於進度和打卡進度的自定義view


如下圖:

這裡寫圖片描述

看GIF豈不是更好

這裡寫圖片描述


這個view在現在的app中挺常見的,基本都是這個套路,


之前寫過一個可以雙向滑動的和這個view的類似,那個滑動的view處理的onTouch事件,以及判斷了我們應該滑動哪個小球,有興趣的可以看下之前的連接
雙向滑動的SeekBarhttp://blog.csdn.net/givemeacondom/article/details/52397589


這個就比較簡單了,都是靜態的繪制,唯一的交互就是UI中的簽到按鈕,點擊一次通知自定義view繪制;


透漏自定義屬性 確定view的size,以及處理測量模式 根據確定的比例,計算我們自定義view中需要的坐標(背景,矩形區域,圓形的白色點,以及選中狀態下的,對號的path坐標) 然後就是繪制,透漏外界設置數據接口

上面就是實現的思路,我們一步步看下代碼,最後會奉上源代碼的下載鏈接;

這是自定義屬性的抽取

 
        
        
        
        
        
    

自定義view中獲取屬性

    private void initAttrs(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SignInView, defStyleAttr, R.style.def_sign_in_style);
        int indexCount = typedArray.getIndexCount();
        for (int i = 0; i < indexCount; i++) {
            int attr = typedArray.getIndex(i);
            switch (attr) {
                case R.styleable.SignInView_sign_in_bg_clor:
                    signInBgColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.SignInView_sign_in_pb_clor:
                    signInPbColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.SignInView_sign_in_check_clor:
                    signInCheckColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.SignInView_sign_in_text_clor:
                    singInTextColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.SignInView_sign_in_text_size:
                    singInTextSize = typedArray.getDimensionPixelSize(attr, 0);
                    break;
            }
        }
        typedArray.recycle();
    }

確定自定義view的大小

根據需求我們的這個view默認充滿屏幕,所以只需要處理height的測量模式即可

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.e("--TAG---", "onMeasure--->>");

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int measureHeight;

        if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
            measureHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_HEIGHT, getResources().getDisplayMetrics());
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(measureHeight, MeasureSpec.EXACTLY);
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

計算我們需要繪制的內容坐標,這個其實是view的思路的最重要的,我們需要知道我們要繪制的東西在那個坐標上,大概就是初中坐標系的知識,回想一下,基本都能繪制出來,至於怎麼繪制,就是谷歌提供給我們的API沒什麼技術難度,

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.e("--TAG---", "onSizeChanged--->>");
        viewPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_PADDING, getResources().getDisplayMetrics());
        int textMarginTop = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, TEXT_MARGIN_TOP, getResources().getDisplayMetrics());
    // 在回調中獲取view的寬度和高度,並計算小球的半徑
        viewWidth = w;
        viewHeight = h;
        signInBallRadio = (int) (viewHeight * SIGN_IN_BALL_SCALE / 2);
        signRectHeight = (int) (signInBallRadio * SIGN_BG_RECT_SCALE);
// 矩形的背景區域,
        signInBgRectF = new RectF(viewPadding + signInBallRadio, viewHeight * SECTION_SCALE - signInBallRadio - signRectHeight, viewWidth - viewPadding - signInBallRadio, viewHeight * SECTION_SCALE - signInBallRadio);

        circleY = (int) (signInBgRectF.top + signRectHeight / 2);
        descY = (int) (viewHeight * SECTION_SCALE) + signInBallRadio + textMarginTop;
        // 核心代碼就一個方法
        calculateCirclePoints(viewData);

        progressRectF = signInPbRectFs.get(0);

    }

計算每個小球的位置

    private void calculateCirclePoints(List viewData) {
    //獲取數據源中分開的份數,比如一周7天,中間的小段是不是有6段,所以是size-1
        if (null != viewData) {
            int intervalSize = viewData.size() - 1;
            // 計算一份的長度,view的寬度減去兩邊的padding,以及總共小球的半徑的綜合然後除以,一共多少份
            int onePiece = (viewWidth - 2 * viewPadding - signInBallRadio * 2 * viewData.size()) / intervalSize;
            // 計算每個小球的坐標,x 和y,
            for (int i = 0; i < viewData.size(); i++) {
            // 這裡其實很簡單,就是一個小小的規律,當前的小球坐標
            // 第一個小球  0個onePiece + 1個小球半徑
            // 第二個小球  1個onePiece + 3個小球半徑
            // 第三個小球  2個onePiece + 5個小球半徑
            // ####........求第N個的距離......?是不是一個送分題
            // 第N個的公式: i * onePiece + ((i + 1) * 2 - 1)*R
                Point circleP = new Point(viewPadding + i * onePiece + ((i + 1) * 2 - 1) * signInBallRadio, circleY);
                Point descP = new Point((int) (viewPadding + i * onePiece + ((i + 1) * 2 - 1) * signInBallRadio - signInTextPaint.measureText(viewData.get(i)) / 2), descY);

// 計算對勾的路徑
                Path signInPath = new Path();
                signInPath.moveTo(circleP.x - signInBallRadio / 2, circleP.y);
                signInPath.lineTo(circleP.x, circleP.y + signInBallRadio / 2);
                signInPath.lineTo(circleP.x + signInBallRadio / 2, circleP.y - signInBallRadio + signInBallRadio / 2);

                RectF signInPbRectF = new RectF(viewPadding + signInBallRadio, viewHeight * SECTION_SCALE - signInBallRadio - signRectHeight, circleP.x, viewHeight * SECTION_SCALE - signInBallRadio);
// 保存計算的坐標;
                signInPaths.add(signInPath);
                circlePoints.add(circleP);
                descPoints.add(descP);
                signInPbRectFs.add(signInPbRectF);
            }
        }
    }

腦細胞累死了,休息休息下,拿著計算好的坐標去canvas繪制吧

 /**
     * 屬性動畫的形式繪制進度條,暫時未啟用,效果預覽可以的
     *
     * @param canvas
     */
    private void drawSignInPbRectWithAnim(final Canvas canvas) {
        if (isNeedReturn()) {
            return;
        }
        final RectF targetRectF = signInPbRectFs.get(currentSignTag);
        RectF beforeRectF;
        if (currentSignTag >= 1) {
            beforeRectF = signInPbRectFs.get(currentSignTag - 1);
            progressRectF.left = beforeRectF.left;
        }
        progressRectF.right = targetRectF.right * persent;
        canvas.drawRect(progressRectF, signInPbPaint);
    }

// 繪制簽到進度的方法,如果想看動畫的可以自己把ondraw中的方法替換成上面的即可
    private void drawSignInPbRectNoAnim(final Canvas canvas) {
        if (isNeedReturn()) {
            return;
        }
        canvas.drawRect(signInPbRectFs.get(currentSignTag), signInPbPaint);
    }

    private boolean isNeedReturn() {
        return currentSignTag < 0 || currentSignTag >= viewData.size();
    }
// 繪制選中的簽到狀態下的圓圈以及白色的對勾路徑
    private void drawSingInCheckCircle(Canvas canvas) {
        if (isNeedReturn()) {
            return;
        }
        for (int i = -1; i < currentSignTag; i++) {
            Point p = circlePoints.get(i + 1);
            Path path = signInPaths.get(i + 1);
            canvas.drawCircle(p.x, p.y, signInBallRadio, signInPbPaint);
            canvas.drawPath(path, signInCheckPaint);
        }
    }
// 繪制簽到信息描述
    private void drawSignDesc(Canvas canvas) {
        for (int i = 0; i < viewData.size(); i++) {
            Point p = descPoints.get(i);
            canvas.drawText(viewData.get(i), p.x, p.y, signInTextPaint);
        }
    }
// 繪制白色的未簽到背景
    private void drawSignInNormalCircle(Canvas canvas) {
        for (Point p : circlePoints) {
            canvas.drawCircle(p.x, p.y, signInBallRadio, signInBgPaint);
        }
    }

    private void drawSignInBgRect(Canvas canvas) {
        canvas.drawRect(signInBgRectF, signInBgPaint);
    }

重要的在這裡,源代碼下載地址

https://github.com/GuoFeilong/BehivorDemo

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved