1. 程式人生 > >android 自定義view之選座功能

android 自定義view之選座功能

效果圖:
這裡寫圖片描述

介面比較粗糙,主要看原理。

這個介面主要包括以下幾部分
1、座位
2、左邊的排數
3、左上方的縮圖
4、縮圖中的紅色區域
5、手指移動時跟隨移動
6、兩個手指縮放時跟隨縮放

主要技術點
1、矩陣Matrix
2、GestureDetector與ScaleGestureDetector
3、Bitmap的一下基本用法
4、這裡只需要重寫view的onDraw就可實現全部功能

可以發現這個其實沒什麼難度,主要就是一些位置的計算。

為了能便於理解首先把要用到的知識點進行一下梳理

1、矩陣Matrix

Matrix由3*3矩陣中9個值來決定,我們對Matrix的所有設定, 就是對這9個值的操作。
{MSCALE_X,MSKEW_X,MTRANS_X,
MSKEW_Y,MSCALE_Y,MTRANS_Y,
MPERSP_0,MPERSP_1,MPERSP_2}

這是矩陣的9個值,看名字也知道他們是什麼意思了。

這裡主要用到縮放和平移,下面以縮放為例來了解一下縮放的控制
通過android提供的api我們可以呼叫setScale、preScale、postScale來改變MSCALE_X和MSCALE_Y的值達到縮放的效果

所以只要理解setScale、preScale、postScale這三個方法的區別我們就可以簡單的進行縮放控制了

1、setScale(sx,sy),首先會將該Matrix設定為對角矩陣,即相當於呼叫reset()方法,然後在設定該Matrix的MSCALE_X和MSCALE_Y直接設定為sx,sy的值
2、preScale(sx,sy),不會重置Matrix,而是直接與Matrix之前的MSCALE_X和MSCALE_Y值結合起來(相乘),M’ = M * S(sx, sy)。
3、postScale(sx,sy),不會重置Matrix,而是直接與Matrix之前的MSCALE_X和MSCALE_Y值結合起來(相乘),M’ = S(sx, sy) * M。

這麼說其實也有些不好理解,舉個栗子一看就明白

1、pre….的執行順序

        Matrix matrix=new Matrix();
        float[] points=new float[]{10.0f,10.0f};
        matrix.preScale(2.0f, 3.0f);
        matrix.preTranslate(8.0f,7.0f);
        matrix.mapPoints(points);
        Log.i("test", points[0]+"");
        Log.i("test", points[1]+""
);

結果為點座標為(36.0,51.0)
可以得出結論,進行變換的順序是先執行preTranslate(8.0f,7.0f),在執行的preScale(2.0f,3.0f)。即對於一個Matrix的設定中,所有pre….是倒著向後執行的。

2、post…的執行順序

        Matrix matrix=new Matrix();
        float[] points=new float[]{10.0f,10.0f};
        matrix.postScale(2.0f, 3.0f);
        matrix.postTranslate(8.0f,7.0f);
        matrix.mapPoints(points);
        Log.i("test", points[0]+"");
        Log.i("test", points[1]+"");

結果為點座標為(28.0,37.0)
可以得出結論,進行變換的順序是先執行postScale(2.0f,3.0f),在執行的postTranslate(8.0f,7.0f)。即對於一個Matrix的設定中,所有post….是順著向前執行的。

這裡主要知道set…和post…方法就行,因為只用到了這兩個。
自我理解其實和scrollTo、scrollBy類似。

2、GestureDetector與ScaleGestureDetector

GestureDetector主要用於識別一些特定手勢,只要呼叫GestureDetector.onTouchEvent()把MotionEvent傳遞進去就可以了
ScaleGestureDetector用於處理縮放的攻擊類用法和GestureDetector類似

3、Bitmap的一下基本用法
參考: 關於bitmap你不知道的一些事

瞭解一下bitmap的注意事項即可

下面開始正式畫這個選座的功能了

1、畫座位:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /**
         * 如果第一次進入 使座點陣圖居中
         */
        if (mViewH != 0 && mViewW != 0&&isFrist) {
            isFrist = false;
            matrix.setTranslate(-(mViewW-getMeasuredWidth())/2, 0);
        }
        /**
         * 畫座位
         */
        drawSeat(canvas);
        /**
         * 畫排數
         */
        drawText(canvas);
        /**
         * 畫縮圖
         */
        drawOverView(canvas);
        /**
         * 畫縮圖選擇區域
         */
        drawOvewRect(canvas);

    }
private void drawSeat(Canvas canvas) {
        float zoom = getMatrixScaleX();
        scale1 = zoom;
        tranlateX = getTranslateX();
        tranlateY = getTranslateY();
        /**
         * 使用兩層for迴圈來畫出所有座位
         */
        for (int i = 0; i < row; i++) {
            float top = i * SeatHight * scale * scale1 + i * mSpaceY * scale1
                    + tranlateY;
            for (int j = 0; j < column; j++) {

                float left = j * SeatWidth * scale * scale1 + j * mSpaceX
                        * scale1 + tranlateX;

                tempMatrix.setTranslate(left, top);
                tempMatrix.postScale(scale, scale, left, top);
                tempMatrix.postScale(scale1, scale1, left, top);


                /**
                 * 獲取每個位置的資訊
                 */
                int state = getSeatType(i, j);
                /**
                 * 根據位置資訊畫不同的位置圖片
                 */
                switch (state) {
                case SEAT_TYPE_SOLD:
                    canvas.drawBitmap(SeatLock, tempMatrix, null);
                    break;
                case SEAT_TYPE_SELECTED:
                    canvas.drawBitmap(SeatChecked, tempMatrix, null);
                    break;
                case SEAT_TYPE_AVAILABLE:
                    canvas.drawBitmap(SeatNormal, tempMatrix, null);
                    break;
                case SEAT_TYPE_NOT_AVAILABLE:
                    break;

                }
            }
        }

    }

這裡其實沒什麼難度,主要就是使用兩層for迴圈,一層畫行,一層畫列

另外要注意的就是當前位置的計算 top = (當前位置i)(座點陣圖標大小SeatHight 它本身的縮放比scale*縮放時的縮放比scale1)+(當前位置i* 垂直方向的間距mSpaceY*縮放時的縮放比scale1)+垂直方向移動是的移動距離

2、畫排數

private void drawText(Canvas canvas) {
        mTextPaint.setColor(bacColor);
        RectF rectF = new RectF();
        rectF.top = getTranslateY() - mNumberHeight/2;
        rectF.bottom = getTranslateY()+ mViewH* getMatrixScaleX() + mNumberHeight/2;
        rectF.left = 0;
        rectF.right = mTextWidth;


        canvas.drawRoundRect(rectF, mTextWidth/2, mTextWidth/2, mTextPaint);
        mTextPaint.setColor(Color.WHITE);
         for (int i = 0; i < row; i++) {
             float top = (i *SeatHight*scale + i * mSpaceY) * getMatrixScaleX() + getTranslateY();
             float bottom = (i * SeatHight*scale + i * mSpaceY + SeatHight) * getMatrixScaleX() + getTranslateY();
             float baseline = (bottom + top  - lineNumberPaintFontMetrics.bottom - lineNumberPaintFontMetrics.top ) / 2-6;
             canvas.drawText(lineNumbers.get(i), mTextWidth / 2, baseline, mTextPaint);
          }     
    }

3、畫縮圖

private void drawOverView(Canvas canvas) {
        /**
         * 1、先畫張背景圖片
         */
        mBitMapOverView = Bitmap.createBitmap((int)mOverViewWidth,(int)mOverViewHight,Bitmap.Config.ARGB_8888);
        Canvas OverViewCanvas = new Canvas(mBitMapOverView);
        Paint paint = new Paint();
        paint.setColor(bacColor);
        scaleoverX = mOverViewWidth / mViewW;
        scaleoverY = mOverViewHight / mViewH;
        float tempX = mViewW * scaleoverX;
        float tempY = mViewH * scaleoverY;
        OverViewCanvas.drawRect(0, 0, (float)tempX, (float)tempY, paint);

        Matrix tempoverMatrix = new Matrix();
        /**
         * 2、和畫座點陣圖一樣在縮圖中畫座位
         */
        for (int i = 0; i < row; i++) {
            float top =  i * SeatHight * scale * scaleoverY+ i * mSpaceY * scaleoverY;
            for (int j = 0; j < column; j++) {
                float left = j * SeatWidth * scale * scaleoverX + j * mSpaceX * scaleoverX+mTextWidth*scaleoverX;
                tempoverMatrix.setTranslate(left, top);
                tempoverMatrix.postScale(scale*scaleoverX, scale*scaleoverY, left, top);

                int state = getSeatType(i, j);
                switch (state) {
                case SEAT_TYPE_SOLD:
                    OverViewCanvas.drawBitmap(SeatLock, tempoverMatrix, null);
                    break;
                case SEAT_TYPE_SELECTED:
                    OverViewCanvas.drawBitmap(SeatChecked, tempoverMatrix, null);
                    break;
                case SEAT_TYPE_AVAILABLE:
                    OverViewCanvas.drawBitmap(SeatNormal, tempoverMatrix, null);
                    break;
                case SEAT_TYPE_NOT_AVAILABLE:
                    break;

                }


            }
        }

        canvas.drawBitmap(mBitMapOverView,0,0,null);

    }

4、縮圖中的紅色區域

private void drawOvewRect(Canvas canvas) {

        OverRectPaint = new Paint();
        OverRectPaint.setColor(Color.RED);
        OverRectPaint.setStyle(Paint.Style.STROKE);
        OverRectPaint.setStrokeWidth(overRectLineWidth);
        int tempViewW ;
        int tempViewH;
        if(getMeasuredWidth()<mViewW){
            tempViewW = getMeasuredWidth();
        }else{
            tempViewW = mViewW;
        }
        if(getMeasuredHeight()<mViewH){
            tempViewH = getMeasuredHeight();
        }else{
            tempViewH = mViewH;
        }

        try{
            Rect rect ;
            if(getMatrixScaleX()>= 1.0f){
                 rect = new Rect((int)(scaleoverX*Math.abs(getTranslateX())/getMatrixScaleX()), 
                                     (int)(scaleoverY*Math.abs(getTranslateY())/getMatrixScaleX()),
                                     (int)(scaleoverX*Math.abs(getTranslateX())/getMatrixScaleX()+tempViewW*scaleoverX/getMatrixScaleX()),
                                     (int)(scaleoverY*Math.abs(getTranslateY())/getMatrixScaleX()+tempViewH*scaleoverY/getMatrixScaleX()));
            }else{
                 rect = new Rect((int)(scaleoverX*Math.abs(getTranslateX())), 
                         (int)(scaleoverY*Math.abs(getTranslateY())),
                         (int)(scaleoverX*Math.abs(getTranslateX())+tempViewW*scaleoverX),
                         (int)(scaleoverY*Math.abs(getTranslateY())+tempViewH*scaleoverY));
            }
        canvas.drawRect(rect, OverRectPaint);
        }catch(Exception e){
            e.printStackTrace();
        }   
    }

5、手指移動時跟隨移動

@Override
    public boolean onTouchEvent(MotionEvent event) {

        /**
         * 縮放事件交由ScaleGestureDetector處理
         */
        scaleGestureDetector.onTouchEvent(event);
        /**
         * 移動和點選事件交由GestureDetector處理
         */
        gestureDetector.onTouchEvent(event);

        return true;
    }
/**
                 * 移動事件 這裡只是簡單判斷了一下,需要更細緻的進行條件判斷
                 */
                public boolean onScroll(MotionEvent e1, MotionEvent e2,
                        float distanceX, float distanceY) {
                    float tempMViewW = column * SeatWidth*scale*scale1+(column -1)*mSpaceX*scale1+mTextWidth-getWidth();
                    float tempmViewH = row * SeatHight * scale * scale1 + (row -1) * mSpaceY * scale1 - getHeight();


                    if((getTranslateX()>mTextWidth+mSpaceX)&& distanceX<0){
                        distanceX = 0.0f;
                    }
                    if((Math.abs(getTranslateX())>tempMViewW)&&(distanceX>0)){
                        distanceX = 0.0f;
                    }


                    if((getTranslateY()>0)&&distanceY<0){
                        distanceY=0.0f;
                    }
                    if((Math.abs(getTranslateY())>tempmViewH)&&(distanceY>0)){
                        distanceY = 0.0f;
                    }                   
                    matrix.postTranslate(-distanceX, -distanceY);               
                    invalidate();
                    return false;

                }
                /**
                 * 單擊事件
                 */
                public boolean onSingleTapConfirmed(MotionEvent e) {
                    int x = (int) e.getX();
                    int y = (int) e.getY();

                    for (int i = 0; i < row; i++) {
                        for (int j = 0; j < column; j++) {
                            int tempX = (int) ((j * SeatWidth * scale + j * mSpaceX) * getMatrixScaleX() + getTranslateX());
                            int maxTemX = (int) (tempX + SeatWidth * scale * getMatrixScaleX());

                            int tempY = (int) ((i * SeatHight * scale + i * mSpaceX) * getMatrixScaleY() + getTranslateY());
                            int maxTempY = (int) (tempY + SeatHight * scale * getMatrixScaleY());


                            if (x >= tempX && x <= maxTemX && y >= tempY
                                    && y <= maxTempY) {
                                int id = getID(i, j);
                                int index = isHave(id);
                                if (index >= 0) {
                                    remove(index);
                                } else {
                                    addChooseSeat(i, j);


                                }
                                float currentScaleY = getMatrixScaleY();
                                if (currentScaleY < 1.7f) {
                                    scaleX = x;
                                    scaleY = y;
                                    /**
                                     * 選中時進行縮放操作
                                     */
                                    zoomAnimate(currentScaleY, 1.9f);
                                }
                                invalidate();
                                break;

                            }
                        }
                    }

                    return super.onSingleTapConfirmed(e);
                }
            });

6、兩個手指縮放時跟隨縮放

public boolean onScale(ScaleGestureDetector detector) {
                    float scaleFactor = detector.getScaleFactor();
                    //scaleX = detector.getCurrentSpanX();
                    //scaleY = detector.getCurrentSpanY();
                    //直接判斷大於2會導致獲取的matrix縮放比例繼續執行一次從而導致變成2.000001之類的數從而使
                    //判斷條件一直為真從而不會執行縮小動作
                    //判斷相乘大於2 可以是當前獲得的縮放比例即使是1.9999之類的數如果繼續放大即使乘以1.0001也會比2大從而
                    //避免上述問題。

                     if (getMatrixScaleY() * scaleFactor > 2) {
                            scaleFactor = 2 / getMatrixScaleY();
                      }
                      if (getMatrixScaleY() * scaleFactor < 0.8) {
                            scaleFactor = 0.8f / getMatrixScaleY();
                      }
                    matrix.postScale(scaleFactor, scaleFactor);


                    invalidate();

                    return true;
                }

至此這個比較粗糙的選座功能就實現了,有時間會繼續優化下細節問題。

下面兩個demo都比較給力我就不上傳demo了

主要參考:
Android例子原始碼高仿QQ電影票選座功能例子

andriod 打造炫酷的電影票線上選座控制元件,1比1還原淘寶電影線上選座功能