Android开发之使用贝塞尔曲线实现黏性水珠下拉效果

标签: 贝塞尔曲线


简介

网上关于贝塞尔曲线的博客和教程很多,通常讲到的三点确定一条曲线:起点终点辅助点

  • 常见的贝塞尔黏性效果

  • 常见的各阶贝塞尔曲线


实现效果

本文所要讲的黏性下拉实现效果如下:

效果计算分析


上图中,分别有四个点,
左边:开始点,
上边:控制点,
下边:结束点,
中间:圆心。
因此可看出,该贝塞尔曲线实际上就是一个二阶贝塞尔曲线(一个控制点)。各点的位置计算以及角度在稍后的代码中将做提供。

代码部分

PullView.java

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.animation.PathInterpolatorCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;/*** Created by shenhua on 2017/10/21.* Email shenhuanet@126.com*/
public class PullView extends View {private Paint mCirclePaint;//圆的画笔private int mCircleRadius = 50;//圆的半径private float mCirclePointX;//圆的xy坐标private float mCirclePointY;private float mProgress;//进度private int mDragHeight = 300;//可拖拽高度private int mTargetWidth = 600;//目标宽度private Path mPath = new Path();//贝塞尔曲线private Paint mPathPaint;private int mTargetGravityHeight = 10;//重心点最终高度,决定控制点的Y坐标private int mTangentAngle = 100;//角度变换 0-135private Interpolator mProgressInterpolator = new DecelerateInterpolator();//进度插值器private Interpolator mTangentAngleInterpolator;//切角路径插值器private Drawable mContent = null;//圆圈内部的圈private int mContentMargin = 10;//圈的边距private ValueAnimator valueAnimator;//释放动画public PullView(Context context) {this(context, null);}public PullView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public PullView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);paint.setAntiAlias(true);//抗锯齿paint.setDither(true);//防抖动paint.setStyle(Paint.Style.FILL);//填充方式paint.setColor(ContextCompat.getColor(getContext(), R.color.colorAccent));mCirclePaint = paint;paint = new Paint(Paint.ANTI_ALIAS_FLAG);//初始化路径部分画笔paint.setAntiAlias(true);paint.setDither(true);paint.setStyle(Paint.Style.FILL);paint.setColor(ContextCompat.getColor(getContext(), R.color.colorAccent));mPathPaint = paint;mTangentAngleInterpolator = PathInterpolatorCompat.create((mCircleRadius * 2.0f) / mDragHeight,90.0f / mTangentAngle);mContent = getResources().getDrawable(R.drawable.circle_drawable);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);updatePathLayout();}private void updatePathLayout() {final float progress = mProgressInterpolator.getInterpolation(mProgress);//获取可绘制区域高度宽度final float w = getValueByLine(getWidth(), mTargetWidth, mProgress);final float h = getValueByLine(0, mDragHeight, mProgress);//X对称轴的参数,圆的圆心坐标,半径等final float cPointX = w / 2;final float cRadius = mCircleRadius;final float cPointY = h - cRadius;//控制点结束Y坐标final float endControlY = mTargetGravityHeight;mCirclePointX = cPointX;mCirclePointY = cPointY;final Path path = mPath;//重置path.reset();path.moveTo(0, 0);//左边部分的结束点和控制点float lEndPointX, lEndPointY;float lControlPointX, lControlPointY;//角度转弧度float angle = mTangentAngle * mTangentAngleInterpolator.getInterpolation(progress);double radian = Math.toRadians(angle);float x = (float) (Math.sin(radian) * cRadius);float y = (float) (Math.cos(radian) * cRadius);lEndPointX = cPointX - x;lEndPointY = cPointY + y;//控制点y坐标变化lControlPointY = getValueByLine(0, endControlY, progress);//控制点与结束定之前的高度float tHeight = lEndPointY - lControlPointY;//控制点与x坐标的距离float tWidth = (float) (tHeight / Math.tan(radian));lControlPointX = lEndPointX - tWidth;//左边贝塞尔曲线path.quadTo(lControlPointX, lControlPointY, lEndPointX, lEndPointY);//连接到右边path.lineTo(cPointX + (cPointX - lEndPointX), lEndPointY);//右边贝塞尔曲线path.quadTo(cPointX + cPointX - lControlPointX, lControlPointY, w, 0);//更新内容部分DrawableupdateContentLayout(cPointX, cPointY, cRadius);}/*** 对内容部分进行测量并设置** @param cx     cPointX* @param cy     cPointY* @param radius cRadius*/private void updateContentLayout(float cx, float cy, float radius) {Drawable drawable = mContent;if (drawable != null) {int margin = mContentMargin;int l = (int) (cx - radius + margin);int r = (int) (cx + radius - margin);int t = (int) (cy - radius + margin);int b = (int) (cy + radius - margin);drawable.setBounds(l, t, r, b);}}/*** 获取当前值** @param start    起点* @param end      终点* @param progress 进度* @return 某一个坐标差值的百分百,计算贝塞尔的关键*/private float getValueByLine(float start, float end, float progress) {return start + (end - start) * progress;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int widthMode = MeasureSpec.getMode(widthMeasureSpec);int width = MeasureSpec.getSize(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int height = MeasureSpec.getSize(heightMeasureSpec);int iHeight = (int) ((mDragHeight * mProgress) + getPaddingTop() + getPaddingBottom());int iWidth = 2 * mCircleRadius + getPaddingLeft() + getPaddingRight();int measureWidth, measureHeight;if (widthMode == MeasureSpec.EXACTLY) {measureWidth = width;} else if (widthMode == MeasureSpec.AT_MOST) {measureWidth = Math.min(iWidth, width);} else {measureWidth = iWidth;}if (heightMode == MeasureSpec.EXACTLY) {measureHeight = height;} else if (heightMode == MeasureSpec.AT_MOST) {measureHeight = Math.min(iHeight, height);} else {measureHeight = iHeight;}setMeasuredDimension(measureWidth, measureHeight);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);int count = canvas.save();float tranX = (getWidth() - getValueByLine(getWidth(), mTargetWidth, mProgress)) / 2;canvas.translate(tranX, 0);canvas.drawPath(mPath, mPathPaint);//画圆canvas.drawCircle(mCirclePointX, mCirclePointY, mCircleRadius, mCirclePaint);Drawable drawable = mContent;if (drawable != null) {canvas.save();//剪切矩形区域canvas.clipRect(drawable.getBounds());//绘制drawable.draw(canvas);canvas.restore();}canvas.restoreToCount(count);}/*** 设置进度** @param progress 进度*/public void setProgress(float progress) {mProgress = progress;requestLayout();}/*** 添加释放动作*/public void release() {if (valueAnimator == null) {ValueAnimator animator = ValueAnimator.ofFloat(mProgress, 0f);animator.setInterpolator(new DecelerateInterpolator());animator.setDuration(400);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {Object val = animation.getAnimatedValue();if (val instanceof Float) {setProgress((Float) val);}}});valueAnimator = animator;} else {valueAnimator.cancel();valueAnimator.setFloatValues(mProgress, 0f);}valueAnimator.start();}
}

circle_drawable.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="oval"><solid android:color="#7FFFFFFF" />
</shape>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><com.shenhua.bezier_demo.PullView
        android:id="@+id/pullView"android:layout_width="match_parent"android:layout_height="wrap_content"/></LinearLayout>

MainActivity.java

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;public class MainActivity extends AppCompatActivity {PullView pullView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);pullView = (PullView) findViewById(R.id.pullView);}private float mTouchStartY;private static final float TOUCH_MOVE_MAX_Y = 300;private static final float SLIPPAGE_FACTOR = 0.5f;// 拖动阻力因子 0~1@Overridepublic boolean onTouchEvent(MotionEvent event) {int action = event.getActionMasked();switch (action) {case MotionEvent.ACTION_DOWN:mTouchStartY = event.getY();return true;case MotionEvent.ACTION_MOVE:float y = event.getY();if (y >= mTouchStartY) {float moveSize = (y - mTouchStartY) * SLIPPAGE_FACTOR;float progress = moveSize >= TOUCH_MOVE_MAX_Y ? 1 : (moveSize / TOUCH_MOVE_MAX_Y);pullView.setProgress(progress);}return true;case MotionEvent.ACTION_UP:pullView.release();return true;default:break;}return false;}
}

总结

贝塞尔曲线在Android中用起来并不难,通常的使用到二阶贝塞尔曲线的创意组合就能实现很多酷炫的效果,曲线的变化就成了很重要的了,需要有很大创意,才能将贝塞尔曲线利用到最完美。

Android开发之使用贝塞尔曲线实现黏性水珠下拉效果相关推荐

  1. 谷歌的android下拉刷新页面,Android SwipeRefreshLayout:谷歌官方SDK包中的下拉刷新

     <Android SwipeRefreshLayout:谷歌官方SDK包中的下拉刷新> 下拉刷新在如今移动开发中应用如此广泛和普遍,以至于谷歌干脆在SDK中给予支持.在android ...

  2. Android模仿QQ的左右滑动切换界面和下拉更新的效果

    转自http://www.linuxidc.com/Linux/2012-08/67207.htm 主布局main.xml <?xml version="1.0" encod ...

  3. android 自定义Scrollview实现淘宝二层楼效果新版微信小程序下拉效果

    android 自定义Scrollview实现淘宝二层楼效果新版微信小程序下拉效果 由于最近一段时间真的是太忙了,没有顾上即使更新博客,还请粉丝们见谅,最近要实现这样一个效果,这个效果跟淘宝二层楼和新 ...

  4. ios学习--iphone开发私房菜_5_] iphone中如何实现下拉菜单 .

    iPhone 中的下拉菜单,或者说选择器一般演变成了Picker,或者UIActionSheet,而前者虽然使用方便,但是占据了非常大的屏幕空间,无法调整.而后者占据空间也很大,而且使用非常受限,垂直 ...

  5. android高级UI之贝塞尔曲线<上>---基本概念、德卡斯特里奥算法

    在上一次android高级UI之Canvas综合案例操练 - cexo - 博客园对于Android UI绘制中核心的Canvas进行了相关的学习,这块的学习也中断一年多了,既然主业是Android开 ...

  6. android 曲线进度条,Android自定义View——使用贝塞尔曲线实现流量进度条

    第一次写带图片的博客,多少还是有点紧张,效果不好,请将就着看,前面的图是今天要写的控件的效果图,元素不多,分别是一个按钮和一个自定义的控件. 在此以前,我看过许多的书,比如<Android群英传 ...

  7. 红橙Darren视频笔记 贝塞尔曲线实现消息拖拽粘性效果 画笔练习

    效果图 要实现这样的效果 我们要先知道那段弧线是如何绘制的,实际上那段弧线就是贝塞尔曲线 一. 什么是贝塞尔曲线 https://www.jianshu.com/p/8f82db9556d2 上面有个 ...

  8. 基于android的网络音乐播放器-下载完成后下拉音乐列表刷新(八)

    作为android初学者,最近把疯狂android讲义和疯狂Java讲义看了一遍,看到书中介绍的知识点非常多,很难全部记住,为了更好的掌握基础知识点,我将开发一个网络音乐播放器-EasyMusic来巩 ...

  9. Android项目:使用pulltorefresh开源项目扩展为下拉刷新上拉加载更多的处理方法,监听listview滚动方向...

    很多android应用的下拉刷新都是使用的pulltorefresh这个开源项目,但是它的扩展性在下拉刷新同时又上拉加载更多时会有一定的局限性.查了很多地方,发现这个开源项目并不能很好的同时支持下拉刷 ...

最新文章

  1. Google学术分析公司科研实力:谷歌1161,华为110,为何差10倍?
  2. 如何根据CSD寄存器计算SD卡容量(csd v1.0 csd v2.0)
  3. 也来玩玩MongoDB
  4. 不借助任何变量进行变量交换
  5. python pptx怎么复制ppt_python pptx复制ppt中的某一页并且放在这一页之后
  6. idea主题颜色Linux,IntelliJ IDEA更换主题样式分享
  7. 我可以做些什么来提高应用程序的可用性?
  8. 我点击一个单选框时另外一个单选框里的内容属性隐藏掉_一个交通工程专业硕士研究生的总结与独白(二):交通生成预测及TransCAD操作...
  9. qTip Style
  10. ftell函数的用法(用于获取指针位置)
  11. 机器学习之Python分析圆周率
  12. 元气骑士里面的超级计算机,元气骑士兑换码2021可用
  13. 20160218.CCPP体系详解(0028天)
  14. 报表设计器是什么?报表自动生成器有那些?
  15. 7种炫酷HTML5 SVG液态水滴融合分解动画特效
  16. 2022江苏省职业院校技能大赛(中职)网络搭建与应用赛项
  17. matlab定义sliced类型,Sliced Variables
  18. Django+itchat+apscheduler实现向指定微信群和微信好友定时发送信息和文件
  19. java0.632数据类型_Object-c提供基本数据类型
  20. composer之创建自己的包

热门文章

  1. UVa Problem 10310 Dog and Gopher (狗拿地鼠)
  2. arcgis_随机数vb脚本
  3. JQuery(js辅助开发类库)
  4. MySQL 的 Buffer Pool,终于被我搞懂了
  5. java8 forkjoinpool_Java 普通线程池与 ForkJoinPool 的效果对比
  6. 光通信的再思考:5G流量爆发下的数据密度革命
  7. 【转】国家集训队论文分类
  8. oracle、mysql创建用户赋DBA权限
  9. 第三十三篇 transition-group 列表过渡
  10. 微信小程序中如何打开公众号文章(node版)