天天看點

仿百度福袋紅包界面

過年了,紅包來襲,時間又是充裕,搶紅包的時候意外發現了百度的福袋界面還不錯,想想還要專門寫一篇博文來完成其界面。

當然啦,這其實就是解鎖界面的進化版本。不過其包含的知識點還是挺多的,寫篇博文記錄一下看看具體有哪些技術點啦。看看百度的效果圖:

仿百度福袋紅包界面

1.程式設計思路

看看界面,不難發現,其就是一個放入九張圖檔的容器,繪制其實可以在其上面另建立一個透明View負責繪制線與圓圈。下面我們将介紹一下實作過程。

㈠自定義ViewGroup

我們知道,自定義ViewGroup一定需要實作其onLayout()方法。該方法是設定子View位置與尺寸的時候調用。還有一個onMeasure()方法,該方法是測量view及其内容來确定view的寬度和高度。

㈡存儲其點與圓的位置及繪制參數

當重回界面的時候,是不會儲存上一次繪制界面的内容,必須存儲以備重繪時候繪制到界面

㈢簡單的縮放動畫

㈣自定義View實作繪制界面

㈤繪制完成時,清除界面繪制内容,并且保證不連接配接重複圖檔

下面我們将完成這些步驟。

2.自定義ViewGroup

開始的任務就是将九張圖檔平均分布到圖檔的位置,顯示在手機界面中。其代碼如下:

public class LYJViewGroup extends ViewGroup implements LYJGestureDrawline.OnAnimationCallback{
    /**
     * 每個點區域的寬度
     */
    private int childWidth;
    /***
     * 上下文
     */
    private Context context;
    /***
     * 儲存圖檔點的位置
     */
    private List<LYJGesturePoint> list;
    /***
     * 建立view使其在ViewGroup之上。
     */
    private LYJGestureView gestureDrawline;
    private int baseNum = 5;
    public LYJViewGroup(Context context) {
        super(context);
        this.context = context;
        this.list = new ArrayList<>();
        DisplayMetrics metric = new DisplayMetrics();
        ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metric);
        childWidth = metric.widthPixels / 3;     // 螢幕寬度(像素)
        addChild();
        // 初始化一個可以畫線的view
        gestureDrawline = new LYJGestureView(context, list);
        gestureDrawline.setAnimationCallback(this);
    }
    public void setParentView(ViewGroup parent){
        // 得到螢幕的寬度
        DisplayMetrics metric = new DisplayMetrics();
        ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metric);
        int width = metric.widthPixels;
        LayoutParams layoutParams = new LayoutParams(width, width);
        this.setLayoutParams(layoutParams);
        gestureDrawline.setLayoutParams(layoutParams);
        parent.addView(this);
        parent.addView(gestureDrawline);
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0; i < getChildCount(); i++) {
            //第幾行
            int rowspan = i / 3;
            //第幾列
            int column = i % 3;
            android.view.View v = getChildAt(i);
            v.layout(column * childWidth + childWidth / baseNum, rowspan * childWidth + childWidth / baseNum,
                    column * childWidth + childWidth - childWidth / baseNum, rowspan * childWidth + childWidth - childWidth / baseNum);
        }
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 周遊設定每個子view的大小
        for (int i = 0; i < getChildCount(); i++) {
            View v = getChildAt(i);
            v.measure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    private void addChild() {
        for (int i = 0; i < 9; i++) {
            ImageView image = new ImageView(context);
            image.setBackgroundResource(R.drawable.marker);
            this.addView(image);
            invalidate();
            // 第幾行
            int rowspan = i / 3;
            // 第幾列
            int column = i % 3;
            // 定義點的左上角與右下角的坐标
            int leftX = column * childWidth + childWidth / baseNum;
            int topY = rowspan * childWidth + childWidth / baseNum;
            int rightX = column * childWidth + childWidth - childWidth / baseNum;
            int bottomY = rowspan * childWidth + childWidth - childWidth / baseNum;
            LYJGesturePoint p = new LYJGesturePoint(leftX, topY, rightX,bottomY,i);
            this.list.add(p);
        }
    }
    
    @Override
    public void startAnimationImage(int i) {
        Animation animation= AnimationUtils.loadAnimation(getContext(), R.anim.gridlayout_child_scale_anim);
        getChildAt(i).startAnimation(animation);
    }
}      

3.自定義點類

顧名思義,就是為了擷取點的相關的屬性,其中基礎屬性圖檔左上角坐标與右下角坐标,計算圖檔中心位置以便擷取圖檔中心點。狀态标記,表示該點是否繪制到圖檔。下面是其實體類:

public class LYJGesturePoint {
    private Point pointLeftTop;//左上角坐标
    private Point pointRightBottom;//右下角坐标
    private int centerX;//圖檔中心點X坐标
    private int centerY;//圖檔中心點Y坐标
    private int pointState;//是否點選了該圖檔

    private int num;

    public int getNum() {
        return num;
    }

    public int getPointState() {
        return pointState;
    }

    public void setPointState(int pointState) {
        this.pointState = pointState;
    }

    public Point getPointLeftTop() {
        return pointLeftTop;
    }

    public Point getPointRightBottom() {
        return pointRightBottom;
    }

    public LYJGesturePoint(int left,int top,int right,int bottom,int i){
        this.pointLeftTop=new Point(left,top);
        this.pointRightBottom=new Point(right,bottom);
        this.num=i;
    }

    public int getCenterX() {
        this.centerX=(this.pointLeftTop.x+this.pointRightBottom.x)/2;
        return centerX;
    }

    public int getCenterY() {
        this.centerY=(this.pointLeftTop.y+this.pointRightBottom.y)/2;
        return centerY;
    }
}
      

4.自定義圓類

這個類較簡單就三個屬性而已(圓中心點坐标及半徑),代碼如下:

public class LYJCirclePoint {
    private int roundX;//圓中心點X坐标
    private int roundY;//圓中心點Y坐标
    private int radiu;//圓半徑

    public int getRadiu() {
        return radiu;
    }

    public int getRoundX() {
        return roundX;
    }

    public int getRoundY() {
        return roundY;
    }

    public LYJCirclePoint(int roundX,int roundY,int radiu){
        this.roundX=roundX;
        this.roundY=roundY;
        this.radiu=radiu;
    }
}
      

5.實作自定義繪制類View

代碼如下:

public class LYJGestureView extends android.view.View {
    /***
     * 聲明直線畫筆
     */
    private Paint paint;
    /***
     * 聲明圓圈畫筆
     */
    private Paint circlePaint;
    /***
     *  畫布
     */
    private Canvas canvas;
    /***
     * 位圖
     */
    private Bitmap bitmap;
    /***
     * 裝有各個view坐标的集合,用于判斷點是否在其中
     */
    private List<LYJGesturePoint> list;
    /***
     * 記錄畫過的線
     */
    private List<Pair<LYJGesturePoint, LYJGesturePoint>> lineList;
    /***
     * 記錄畫過的圓
     */
    private List<LYJCirclePoint> circlePoints;
    /**
     * 手指目前在哪個Point内
     */
    private LYJGesturePoint currentPoint;
    /***
     * 手指按下動畫
     */
    private OnAnimationCallback animationCallback;
    public interface OnAnimationCallback{
        public void startAnimationImage(int i);
    }

    public void setAnimationCallback(OnAnimationCallback animationCallback) {
        this.animationCallback = animationCallback;
    }

    public LYJGestureView(Context context, List<LYJGesturePoint> list){
        super(context);
        Log.i(getClass().getName(), "GestureDrawline");
        paint = new Paint(Paint.DITHER_FLAG);// 建立一個畫筆
        circlePaint=new Paint(Paint.DITHER_FLAG);
        DisplayMetrics metric = new DisplayMetrics();
        ((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(metric);
        Log.i(getClass().getName(), "widthPixels" + metric.widthPixels);
        Log.i(getClass().getName(), "heightPixels" + metric.heightPixels);
        bitmap = Bitmap.createBitmap(metric.widthPixels, metric.heightPixels, Bitmap.Config.ARGB_8888); // 設定位圖的寬高
        canvas = new Canvas();
        canvas.setBitmap(bitmap);
        paint.setStyle(Paint.Style.STROKE);// 設定非填充
        paint.setStrokeWidth(20);// 筆寬20像素
        paint.setColor(Color.rgb(245, 142, 33));// 設定預設連線顔色
        paint.setAntiAlias(true);// 不顯示鋸齒
        circlePaint.setStyle(Paint.Style.FILL);
        circlePaint.setStrokeWidth(1);
        circlePaint.setAntiAlias(true);
        circlePaint.setColor(Color.rgb(245, 142, 33));

        this.list = list;
        this.lineList = new ArrayList<>();
        this.circlePoints=new ArrayList<>();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                // 判斷目前點選的位置是處于哪個點之内
                currentPoint = getPointAt((int) event.getX(), (int) event.getY());
                if (currentPoint != null) {
                    currentPoint.setPointState(Constants.POINT_STATE_SELECTED);
                    this.animationCallback.startAnimationImage(currentPoint.getNum());
                    canvas.drawCircle(currentPoint.getCenterX(), currentPoint.getCenterY(), 20, circlePaint);
                    circlePoints.add(new LYJCirclePoint(currentPoint.getCenterX(),currentPoint.getCenterY(),20));
                }
                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:
                clearScreenAndDrawList();
                // 得到目前移動位置是處于哪個點内
                LYJGesturePoint pointAt = getPointAt((int) event.getX(), (int) event.getY());
                if (currentPoint == null && pointAt == null) {//你把手指按在螢幕滑動,如果終點與起點都不圖檔那麼傳回
                    return true;
                } else {// 代表使用者的手指移動到了點上
                    if (currentPoint == null) {// 先判斷目前的point是不是為null
                        // 如果為空,那麼把手指移動到的點指派給currentPoint
                        currentPoint = pointAt;
                        // 把currentPoint這個點設定選中狀态;
                        currentPoint.setPointState(Constants.POINT_STATE_SELECTED);
                    }
                }
                //如果移動到的點不為圖檔區域或者移動到自己的地方,或者該圖檔已經為選中狀态,直接畫直線就可以了
                if(pointAt == null || currentPoint.equals(pointAt) || Constants.POINT_STATE_SELECTED == pointAt.getPointState()){
                    canvas.drawCircle(currentPoint.getCenterX(), currentPoint.getCenterY(), 20, circlePaint);
                    circlePoints.add(new LYJCirclePoint(currentPoint.getCenterX(), currentPoint.getCenterY(), 20));
                    canvas.drawLine(currentPoint.getCenterX(), currentPoint.getCenterY(), event.getX(), event.getY(), paint);
                }else{//其他情況畫兩點相連直線,并且儲存繪制圓與直線,并調用按下圖檔的縮放動畫
                    canvas.drawCircle(pointAt.getCenterX(),pointAt.getCenterY(),20,circlePaint);
                    circlePoints.add(new LYJCirclePoint(pointAt.getCenterX(), pointAt.getCenterY(), 20));
                    this.animationCallback.startAnimationImage(pointAt.getNum());
                    pointAt.setPointState(Constants.POINT_STATE_SELECTED);
                    canvas.drawLine(currentPoint.getCenterX(), currentPoint.getCenterY(), pointAt.getCenterX(), pointAt.getCenterY(), paint);
                    Pair<LYJGesturePoint, LYJGesturePoint> pair = new Pair<>(currentPoint, pointAt);
                    lineList.add(pair);
                    currentPoint=pointAt;//設定選中點為目前點。
                }
                invalidate();//重繪
                break;
            case MotionEvent.ACTION_UP:
                clearScreenAndDrawList();//防止多出一條沒有終點的直線
                new Handler().postDelayed(new clearLineRunnable(), 1000);//1秒後清空繪制界面
                invalidate();//重繪
                break;
            default:
                break;
        }
        return true;
    }

    class clearLineRunnable implements Runnable {
        public void run() {
            // 清空儲存點與圓的集合
            lineList.clear();
            circlePoints.clear();
            // 重新繪制界面
            clearScreenAndDrawList();
            for (LYJGesturePoint p : list) {
                //設定其為初始化不選中狀态
                p.setPointState(Constants.POINT_STATE_NORMAL);
            }
            invalidate();
        }
    }

    /**
     * 通過點的位置去集合裡面查找這個點是包含在哪個Point裡面的
     *
     * @param x
     * @param y
     * @return 如果沒有找到,則傳回null,代表使用者目前移動的地方屬于點與點之間
     */
    private LYJGesturePoint getPointAt(int x, int y) {

        for (LYJGesturePoint point : list) {
            // 先判斷點是否在圖檔的X坐标内
            int leftX = point.getPointLeftTop().x;
            int rightX = point.getPointRightBottom().x;
            if (!(x >= leftX && x < rightX)) {
                // 如果為假,則跳到下一個對比
                continue;
            }
            //在判斷點是否在圖檔的Y坐标内
            int topY = point.getPointLeftTop().y;
            int bottomY = point.getPointRightBottom().y;
            if (!(y >= topY && y < bottomY)) {
                // 如果為假,則跳到下一個對比
                continue;
            }

            // 如果執行到這,那麼說明目前點選的點的位置在周遊到點的位置這個地方
            return point;
        }

        return null;
    }

    /**
     * 清掉螢幕上所有的線,然後畫出集合裡面的線
     */
    private void clearScreenAndDrawList() {
        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        for (Pair<LYJGesturePoint, LYJGesturePoint> pair : lineList) {
            canvas.drawLine(pair.first.getCenterX(), pair.first.getCenterY(),
                    pair.second.getCenterX(), pair.second.getCenterY(), paint);// 畫線
        }
        for(LYJCirclePoint lyjCirclePoint : circlePoints){
            canvas.drawCircle(lyjCirclePoint.getRoundX(),lyjCirclePoint.getRoundY(), lyjCirclePoint.getRadiu(),circlePaint);
        }
    }
    //繪制用bitmap建立出來的畫布
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawBitmap(bitmap, 0, 0, null);
    }
}      

這樣就可以得到如下界面效果(當然反編譯百度錢包,并沒有百度錢包中的圖檔,隻好随便找了一張圖檔):

附上本文源碼: