canvas仿写小黄鸡,弹性效果
该项目主要实现了canvas绘制及小黄鸡会看向我们的手指以及上下弹跳等比较有意思的效果 我的github项目地址 Johncuiqiang/-.git先看一下效果
1530497087285477.gif
代码导读
pointsBean类中主要实现了两个方法
初始化基本绘制的坐标点,包括头,身体,眼睛,鼻子的中心点,半径等相关参数,关键为理解决android的屏幕适配问题,我们先要获取屏幕宽高,而后依据获取到的屏幕参数动态初始化我们的坐标参数
private void initPoint(){ mBodyWidth = mScreenWidth /3; mBodyHeight = mScreenHeight /4; mLeft = (int) (mScreenWidth /3); mRight = (int) (mLeft + mBodyWidth); mTop = (int) (3* mScreenHeight /8); .......初始化我们的限制参数,限制参数就是在我们手指移动的过程中眼睛的上下左右移动的最大值,到达这个阈值,会进行一个加速度动画回弹到原位,原理相似于:手指移动5,图像移动1
private void initOtherParams() { upScrollEdge = (int) (mBodyHeight /4f); downScrollEdge = (int) (mBodyHeight /2f); upScrollEye = (int) (upScrollEdge + mCircleR /6f); downScrollEye = (int) (downScrollEdge + mCircleR /5f); leftScrollEdge = (int) (mCircleR /3f); rightScrollEdge = (int) (mCircleR /3f); mBodyRatio = mScreenHeight / mBodyHeight +1; mEyeRatio = mBodyRatio * downScrollEdge / downScrollEye;//手指滑动总距离是一样的}BezierView类中,我们进行了对形象的绘制,动画效果的实现等个功能 我们做完了我们初始化的功能,接下在ondraw方法中进行绘制我们的body形象
@Overrideprotected void onDraw(Canvas canvas){ super.onDraw(canvas); mPaint.setColor(mBodyColor); mPaint.setStrokeWidth(UIUtils.dp2px(5)); mPaint.setStyle(Paint.Style.FILL); drawHead(canvas); drawBody(canvas); drawEye(canvas); drawNose(canvas);}主要举两个例子,画身体,这个使用贝塞尔曲线画的
什么是贝塞尔曲线?
这里简单解释一下,比方一条线有三个点,线的两头为开始和结束点,中间是一个点,通过拖拽中间这个点,达到线的凹凸变化。
就像小时候美术课画大山一样,你想让山高点,就把中间的点多往上画点,反之亦然,怎样样这个解释够形象把
/** *画body的身体* @param canvas canvas对象 */private void drawBody(Canvas canvas) {//重置路径mPath.reset();//起点mPath.moveTo(mStartPointLeft.x, mStartPointLeft.y);mPath.quadTo(mAssistPointLeft.x, mAssistPointLeft.y, mEndPointLeft.x, mEndPointLeft.y);mPath.lineTo(mEndPointRight.x, mEndPointRight.y);mPath.quadTo(mAssistPointRight.x, mAssistPointLeft.y, mStartPointRight.x, mStartPointLeft.y);mPath.lineTo(mStartPointLeft.x, mStartPointLeft.y);//画路径canvas.drawPath(mPath, mPaint); }画眼睛,绘制眼睛相对来说较好实现,drawCircle是google提供的绘制圆行的api,只是较为麻烦,眼睛还有眼珠等参数较多,后面的绘制就不上代码了
/** * 画眼睛 * @param canvas canvas对象 */private void drawEye(Canvas canvas) { //绘制外眼眶mPaint.setColor(Color.BLACK); mPaint.setStrokeWidth(UIUtils.dp2px(3)); mPaint.setStyle(Paint.Style.STROKE); canvas.drawCircle(mLeftEyePoint.x, mLeftEyePoint.y, mCircleR /6, mPaint); canvas.drawCircle(mRightEyePoint.x, mRightEyePoint.y, mCircleR /6, mPaint); //绘制白眼球mPaint.setColor(Color.WHITE); mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(mLeftEyePoint.x, mLeftEyePoint.y, mCircleR /6, mPaint); canvas.drawCircle(mRightEyePoint.x, mRightEyePoint.y, mCircleR /6, mPaint); //绘制黑眼球 mPaint.setColor(Color.BLACK);mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(mLeftEyeBallPoint.x, mLeftEyeBallPoint.y, mCircleR /12, mPaint); canvas.drawCircle(mRightEyeBallPoint.x, mLeftEyeBallPoint.y, mCircleR /12, mPaint); //绘制瞳孔(白色) mPaint.setColor(Color.WHITE); mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(mLeftEyeBallPoint.x, mLeftEyeBallPoint.y - mCircleR /24, mCircleR /30, mPaint); canvas.drawCircle(mRightEyeBallPoint.x, mRightEyeBallPoint.y - mCircleR /24, mCircleR /30, mPaint); }该方法根据之前初始话的左右限定边界,滑动限制参数,以及具体的手指滑动距离,计算出body身体各部分应该移动的距离
/** * 得到限制距离 * * @param allDistance 手指移动的距离 * @param ratio 滑动限制参数 * @param leftUpEdge 左限定边界 * @param rightDownEdge 右限定边界 * @return 计算后的移动距离 */ private float getAllDiff(float allDistance, float ratio, int leftUpEdge, int rightDownEdge) { //限定边界 if (allDistance < -ratio * leftUpEdge) { allDistance = -ratio * leftUpEdge; } if (allDistance > ratio * rightDownEdge) { allDistance = ratio * rightDownEdge; } //计算图像y轴更改值 allDiff float fraction; float allDiff; if (allDistance >= 0) { //下滑或者右滑 fraction = 1f * allDistance / (ratio * rightDownEdge); float interpolation = mInterpolator.getInterpolation(fraction); allDiff = rightDownEdge * interpolation; } else { fraction = 1f * Math.abs(allDistance) / (ratio * leftUpEdge); float interpolation = mInterpolator.getInterpolation(fraction); allDiff = -leftUpEdge * interpolation; } return allDiff;}对原始参数和变化参数进行存储,方便之后做回位和回弹动画的效果
/** * 由于参数比较多,所以把原始参数和变化后的参数,存起来传到执行动画中 */ private PropertyValuesHolder[] getValuesHolder() { PropertyValuesHolder[] holders = new PropertyValuesHolder[mResetList.size() + mResetList.size()]; for (int i = 0; i < mResetList.size(); i++) { MyPoint point = mResetList.get(i); PropertyValuesHolder holderY = PropertyValuesHolder.ofFloat(point.getPointName() + ".y", point.y, point.oy); holders[i] = holderY; PropertyValuesHolder holderX = PropertyValuesHolder.ofFloat(point.getPointName() + ".x", point.x, point.ox); //如果size = 8,单存y方向的参数可存8个,最后一个索引为7,从索引8+i = 8,9,10...开始存x方向参数 holders[mResetList.size() + i] = holderX; } return holders; }滑动监听事件
- 在ACTION_DOWN中我们记录一下客户的初始值
- 在ACTION_MOVE我们执行随手动以及眼睛跟随动画,在手指移动的过程中我们不断的改变我们的body各个坐标的值并不断重新绘制,并解决少量我们遇到的问题,比方客户在短时间内进行了屡次滑动,第二次滑动开始的时候,假如第一次滑动没有执行完,我们要把第一次的滑动中止掉,重置动画也要中止掉,通过中间变量的判断实现就可
- 在ACTION_UP中我们在移动距离置0,执行重置动画
@Override public boolean onTouchEvent(MotionEvent event) { int downY = (int) event.getRawY(); int downX = (int) event.getRawX(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastY = downY; lastEyeX = downX; lastEyeY = downY; break; case MotionEvent.ACTION_MOVE: Log.d(TAG," ACTION_MOVE"); if (!isResetFinish) { //重置动画假如没有执行完,中止动画 Log.d(TAG," isResetFinish"); //当手指滑动了两次,中止第一次滑动的效果,否则会出现划不动的情况 isAbortAnim = true; //判断动画能否执行完,让isAbortAnim为false或者true isResetFinish = true; } //判断能否是点击还是移动 isPointerMove = true; moveBody(event); moveFace(event); //重新绘制方法 invalidate(); break; case MotionEvent.ACTION_UP: allDy = 0; allEyeDx = 0; allEyeDy = 0; Log.d(TAG," ACTION_UP"); //解开动画禁制 isAbortAnim = false; if(isPointerMove) { reset(getValuesHolder()); } break; default: break; } return true; }移动身体逻辑的实现
/** * 移动body的身体 * @param event 手指滑动事件 */ private void moveBody(MotionEvent event) { moveY = (int) event.getRawY(); int dy = moveY - lastY; allDy = allDy + dy; float allDiff = getAllDiff(allDy, mBodyRatio, UP_SCROLL_BODY_DEGE, DOWN_SCROLL_BODY_DEGE); //更改各点y坐标 mStartPointLeft.y = (int) (mStartPointLeft.oy + allDiff); mAssistPointLeft.y = (int) (mAssistPointLeft.oy + allDiff / 2); mCirclePoint.y = (int) (mCirclePoint.oy + allDiff); lastY = moveY; }重置回弹动画的执行,ValueAnimator这个类可以监听到属性动画改变值,一旦发现value的变化值与我们之前point存储的原有值相等,则中止执行回置动画,为了让动画效果更好,还添加了动画插值器OvershootInterpolator向前甩肯定值后再回到原来位置,让动画具备弹性
/** * 重置回弹动画 * @param holders 之前记录的原始参数 */ private void reset(PropertyValuesHolder[] holders) { final ValueAnimator animator;// 动画器 animator = ValueAnimator.ofPropertyValuesHolder(holders); // 动画升级的监听 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator arg0) { for (int i = 0; i < mResetList.size(); i++) { MyPoint point = mResetList.get(i); float valueY = (Float) arg0.getAnimatedValue(point.getPointName() + ".y"); float valueX = (Float) arg0.getAnimatedValue(point.getPointName() + ".x"); if (!isAbortAnim) { //执行动画的核心方法 point.x = (int) valueX; if (i > 2) {//前3个参数为body参数,不需要x方向移动 invalidate(); } //动画执行完成 isResetFinish = point.x == point.ox; Log.d(TAG," onAnimationUpdate"); isPointerMove = false; } // 根据最新value,升级布局 if (!isAbortAnim) { point.y = (int) valueY; invalidate(); //动画执行完成 isResetFinish = point.y == point.oy; isPointerMove = false; } } } }); animator.setDuration(500);// 动画时间 animator.setInterpolator(new OvershootInterpolator()); animator.start();// 开启动画 }1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » canvas仿写小黄鸡,弹性效果