过年了,红包来袭,时间又是充裕,抢红包的时候意外发现了百度的福袋界面还不错,想想还要专门写一篇博文来完成其界面。
当然啦,这其实就是解锁界面的进化版本。不过其包含的知识点还是挺多的,写篇博文记录一下看看具体有哪些技术点啦。看看百度的效果图:
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); } }
这样就可以得到如下界面效果(当然反编译百度钱包,并没有百度钱包中的图片,只好随便找了一张图片):
附上本文源码:
http://download.csdn.net/detail/liyuanjinglyj/9426567