SeniorUI09

SeniorUI09

高级UI汇总​​​​​​​
源码:SeniorUI09_BezierActivity

1 效果图

2 贝塞尔曲线简介

以简单的二阶贝塞尔曲线为例
在平面内任选 3 个不共线的点,依次用线段连接。

在第一条线段上任选一个点 D。计算该点到线段起点的距离 AD,与该线段总长 AB 的比例;根据上一步得到的比例,从第二条线段上找出对应的点 E

连接这两点 DE。从新的线段 DE 上再次找出相同比例的点 F,使得 DF:DE = AD:AB = BE:BC。

到这里,我们就确定了贝塞尔曲线上的一个点 F。接下来,请稍微回想一下中学所学的极限知识,让选取的点 D 在第一条线段上从起点 A 移动到终点 B,找出所有的贝塞尔曲线上的点 F。所有的点找出来之后,我们也得到了这条贝塞尔曲线。

动态效果:

如果增加点的个数则多取几次连线的比例。
贝塞尔曲线起源于汽车设计,在计算机图形学领域有很多应用,也可以用来模拟各种效果,安卓中封装好了对应的API

3 需求

  • 实现类型QQ消息气泡效果
  • 可以设置消息数量,监听点击事件
  • 拉动“气泡”一定距离内还原,超出距离有“气泡破裂效果”

4 原理

  • 通过自定义View实现该效果;消息圆圈的大小、颜色,消息字体、颜色、大小通过自定义属性设置
  • 整个逻辑分4个部分
    1、画静止状态(画圆)
    2、画相连状态,两个圆加用贝塞尔曲线连接(监听事件,计算距离,逐渐改变圆大小,两圆心的中间点为贝塞尔曲线的节点)

    3、画分离状态(超出距离画两个圆,手指离开)
    4、画消失状态—爆炸动画(inAnimation动画)

5 核心代码

public class DragBubbleView extends View {/*** 气泡默认状态--静止*/private final int BUBBLE_STATE_DEFAUL = 0;/*** 气泡相连*/private final int BUBBLE_STATE_CONNECT = 1;/*** 气泡分离*/private final int BUBBLE_STATE_APART = 2;/*** 气泡消失*/private final int BUBBLE_STATE_DISMISS = 3;/*** 气泡半径*/private float mBubbleRadius;/*** 气泡颜色*/private int mBubbleColor;/*** 气泡消息文字*/private String mTextStr;/*** 气泡消息文字颜色*/private int mTextColor;/*** 气泡消息文字大小*/private float mTextSize;/*** 不动气泡的半径*/private float mBubStillRadius;/*** 可动气泡的半径*/private float mBubMoveableRadius;/*** 不动气泡的圆心*/private PointF mBubStillCenter;/*** 可动气泡的圆心*/private PointF mBubMoveableCenter;/*** 气泡的画笔*/private Paint mBubblePaint;/*** 贝塞尔曲线path*/private Path mBezierPath;private Paint mTextPaint;private Rect mTextRect;private Paint mBurstPaint;private Rect mBurstRect;/*** 气泡状态标志*/private int mBubbleState = BUBBLE_STATE_DEFAUL;/*** 两气泡圆心距离*/private float mDist;/*** 气泡相连状态最大圆心距离*/private float mMaxDist;/*** 手指触摸偏移量*/private final float MOVE_OFFSET;/***  气泡爆炸的bitmap数组*/private Bitmap[] mBurstBitmapsArray;/*** 是否在执行气泡爆炸动画*/private boolean mIsBurstAnimStart = false;/*** 当前气泡爆炸图片index*/private int mCurDrawableIndex;/***  气泡爆炸的图片id数组*/private int[] mBurstDrawablesArray = {R.drawable.burst_1, R.drawable.burst_2, R.drawable.burst_3, R.drawable.burst_4, R.drawable.burst_5};public DragBubbleView(Context context) {this(context,null);}public DragBubbleView(Context context, @Nullable AttributeSet attrs) {this(context, attrs,0);}public DragBubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {this(context, attrs, defStyleAttr,0);}@TargetApi(Build.VERSION_CODES.LOLLIPOP)public DragBubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.DragBubbleView,defStyleAttr,0);mBubbleRadius = array.getDimension(R.styleable.DragBubbleView_bubble_radius,mBubbleRadius);mBubbleColor = array.getColor(R.styleable.DragBubbleView_bubble_color, Color.RED);mTextStr = array.getString(R.styleable.DragBubbleView_bubble_text);mTextSize = array.getDimension(R.styleable.DragBubbleView_bubble_textSize,mTextSize);mTextColor = array.getColor(R.styleable.DragBubbleView_bubble_textColor, Color.WHITE);array.recycle();mBubStillRadius = mBubbleRadius;mBubMoveableRadius = mBubStillRadius;mMaxDist = 8 * mBubbleRadius;MOVE_OFFSET = mMaxDist / 4;//抗锯齿mBubblePaint = new Paint(Paint.ANTI_ALIAS_FLAG);mBubblePaint.setColor(mBubbleColor);mBubblePaint.setStyle(Paint.Style.FILL);mBezierPath = new Path();mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mTextPaint.setColor(mTextColor);mTextPaint.setTextSize(mTextSize);mTextRect = new Rect();mBurstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mBurstPaint.setFilterBitmap(true);mBurstRect = new Rect();mBurstBitmapsArray = new Bitmap[mBurstDrawablesArray.length];for (int i = 0; i < mBurstDrawablesArray.length; i++) {//将气泡爆炸的drawable转为bitmapBitmap bitmap = BitmapFactory.decodeResource(getResources(), mBurstDrawablesArray[i]);mBurstBitmapsArray[i] = bitmap;}}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);initView(w,h);}/*** 初始化气泡位置* @param w* @param h*/private void initView(int w, int h) {//设置两气泡圆心初始坐标if(mBubStillCenter == null){mBubStillCenter = new PointF(w / 2,h / 2);}else{mBubStillCenter.set(w / 2,h / 2);}if(mBubMoveableCenter == null){mBubMoveableCenter = new PointF(w / 2,h / 2);}else{mBubMoveableCenter.set(w / 2,h / 2);}mBubbleState = BUBBLE_STATE_DEFAUL;}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()){case MotionEvent.ACTION_DOWN:{if(mBubbleState != BUBBLE_STATE_DISMISS){mDist = (float) Math.hypot(event.getX() - mBubStillCenter.x,event.getY() - mBubStillCenter.y);if(mDist < mBubbleRadius + MOVE_OFFSET){// 加上MOVE_OFFSET是为了方便拖拽mBubbleState = BUBBLE_STATE_CONNECT;}else{mBubbleState = BUBBLE_STATE_DEFAUL;}}}break;case MotionEvent.ACTION_MOVE:{if(mBubbleState != BUBBLE_STATE_DEFAUL){mBubMoveableCenter.x = event.getX();mBubMoveableCenter.y = event.getY();mDist = (float) Math.hypot(event.getX() - mBubStillCenter.x,event.getY() - mBubStillCenter.y);if(mBubbleState == BUBBLE_STATE_CONNECT){// 减去MOVE_OFFSET是为了让不动气泡半径到一个较小值时就直接消失// 或者说是进入分离状态if(mDist < mMaxDist - MOVE_OFFSET){mBubStillRadius = mBubbleRadius - mDist / 8;}else{mBubbleState = BUBBLE_STATE_APART;}}invalidate();}}break;case MotionEvent.ACTION_UP:{if(mBubbleState == BUBBLE_STATE_CONNECT){startBubbleRestAnim();}else if(mBubbleState == BUBBLE_STATE_APART){if(mDist < 2 * mBubbleRadius){startBubbleRestAnim();}else{startBubbleBurstAnim();}}}break;}return true;}private void startBubbleBurstAnim() {//气泡改为消失状态mBubbleState = BUBBLE_STATE_DISMISS;mIsBurstAnimStart = true;//做一个int型属性动画,从0~mBurstDrawablesArray.length结束ValueAnimator anim = ValueAnimator.ofInt(0, mBurstDrawablesArray.length);anim.setInterpolator(new LinearInterpolator());anim.setDuration(500);anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {//设置当前绘制的爆炸图片indexmCurDrawableIndex = (int) animation.getAnimatedValue();invalidate();}});anim.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {//修改动画执行标志mIsBurstAnimStart = false;}});anim.start();}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)private void startBubbleRestAnim() {ValueAnimator anim = ValueAnimator.ofObject(new PointFEvaluator(),new PointF(mBubMoveableCenter.x,mBubMoveableCenter.y),new PointF(mBubStillCenter.x,mBubStillCenter.y));anim.setDuration(200);anim.setInterpolator(new OvershootInterpolator(5f));anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mBubMoveableCenter = (PointF) animation.getAnimatedValue();invalidate();}});anim.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {mBubbleState = BUBBLE_STATE_DEFAUL;}});anim.start();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 1、画静止状态// 2、画相连状态// 3、画分离状态// 4、画消失状态---爆炸动画// 1、画拖拽的气泡 和 文字if(mBubbleState != BUBBLE_STATE_DISMISS){canvas.drawCircle(mBubMoveableCenter.x,mBubMoveableCenter.y,mBubMoveableRadius,mBubblePaint);mTextPaint.getTextBounds(mTextStr,0,mTextStr.length(),mTextRect);canvas.drawText(mTextStr,mBubMoveableCenter.x - mTextRect.width() / 2,mBubMoveableCenter.y + mTextRect.height() / 2,mTextPaint);}// 2、画相连的气泡状态if(mBubbleState == BUBBLE_STATE_CONNECT){// 1、画静止气泡canvas.drawCircle(mBubStillCenter.x,mBubStillCenter.y,mBubStillRadius,mBubblePaint);// 2、画相连曲线// 计算控制点坐标,两个圆心的中点int iAnchorX = (int) ((mBubStillCenter.x + mBubMoveableCenter.x) / 2);int iAnchorY = (int) ((mBubStillCenter.y + mBubMoveableCenter.y) / 2);float cosTheta = (mBubMoveableCenter.x - mBubStillCenter.x) / mDist;float sinTheta = (mBubMoveableCenter.y - mBubStillCenter.y) / mDist;float iBubStillStartX = mBubStillCenter.x - mBubStillRadius * sinTheta;float iBubStillStartY = mBubStillCenter.y + mBubStillRadius * cosTheta;float iBubMoveableEndX = mBubMoveableCenter.x - mBubMoveableRadius * sinTheta;float iBubMoveableEndY = mBubMoveableCenter.y + mBubMoveableRadius * cosTheta;float iBubMoveableStartX = mBubMoveableCenter.x + mBubMoveableRadius * sinTheta;float iBubMoveableStartY = mBubMoveableCenter.y - mBubMoveableRadius * cosTheta;float iBubStillEndX = mBubStillCenter.x + mBubStillRadius * sinTheta;float iBubStillEndY = mBubStillCenter.y - mBubStillRadius * cosTheta;mBezierPath.reset();// 画上半弧mBezierPath.moveTo(iBubStillStartX,iBubStillStartY);mBezierPath.quadTo(iAnchorX,iAnchorY,iBubMoveableEndX,iBubMoveableEndY);// 画上半弧mBezierPath.lineTo(iBubMoveableStartX,iBubMoveableStartY);mBezierPath.quadTo(iAnchorX,iAnchorY,iBubStillEndX,iBubStillEndY);mBezierPath.close();canvas.drawPath(mBezierPath,mBubblePaint);}// 3、画消失状态---爆炸动画if(mIsBurstAnimStart){mBurstRect.set((int)(mBubMoveableCenter.x - mBubMoveableRadius),(int)(mBubMoveableCenter.y - mBubMoveableRadius),(int)(mBubMoveableCenter.x + mBubMoveableRadius),(int)(mBubMoveableCenter.y + mBubMoveableRadius));canvas.drawBitmap(mBurstBitmapsArray[mCurDrawableIndex],null,mBurstRect,mBubblePaint);}/*// 2、画相连状态// 1、画静止气泡// 2、画文字// 3、画相连曲线// 4、画拖拽气泡// 3、画分离状态// 1、画文字// 2、画拖拽气泡// 4、画消失状态---爆炸动画*/}public void reset() {initView(getWidth(),getHeight());invalidate();}
}

发布者:admin,转转请注明出处:http://www.yc00.com/news/1701033510a1046229.html

相关推荐

  • SeniorUI09

    2023-11-27
    200

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信