新年到新年到,紅包搶不停。在我搶紅包的時候意外的發現了百度的福袋界面挺不錯的,于是抽時間專門寫篇文章來完成百度紅包界面吧。
當然啦,這其實就是解鎖界面的進化版本。不過其包含的知識點還是挺多的,寫篇博文記錄一下看看具體有哪些技術點啦。看看百度的效果圖:
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();// 初始化一個可以畫線的viewgestureDrawline = 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);}@Overrideprotected 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);}}@Overrideprotected 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);}}@Overridepublic 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<>();}@Overridepublic 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// 如果為空,那么把手指移動到的點賦值給currentPointcurrentPoint = 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創建出來的畫布@Overrideprotected void onDraw(Canvas canvas) {canvas.drawBitmap(bitmap, 0, 0, null);}}
這樣就可以得到如下界面效果(當然反編譯百度錢包,并沒有百度錢包中的圖片,只好隨便找了一張圖片):
新聞熱點
疑難解答