自定义View-仿虎扑直播比赛界面的打赏按钮

作为一个资深篮球爱好者,我经常会用虎扑app看比赛直播,后来注意到文字直播界面右下角加了两个按钮,可以在直播过程中送虎扑币,为自己支持的球队加油,具体的效果如下图所示:

我个人觉得挺好玩的,所以决定自己实现下这个按钮,废话不多说,先看实现的效果吧:

这个效果看起来和popupwindow差不多,但我是采用自定义view的方式来实现,下面说说过程。

首先从虎扑的效果可以看到,它这两个按钮时浮在整个界面之上的,所以它需要和FrameLayout结合使用,因此我让它的宽度跟随屏幕大小,高度根据dpi固定,它的实际尺寸时这样的:

另外这个view初始化出来我们看到可以分为三块,背景圆、圆内文字、圆上方数字,所以正常状态下,只需要在onDraw方法中画出这三块内容即可。先在初始化方法中将自定义的属性和画笔以及初始化数据准备好:


  1. private void init(Context context, AttributeSet attrs) { 
  2. //获取自定义属性 
  3. TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.HoopView); 
  4. mThemeColor = typedArray.getColor(R.styleable.HoopView_theme_color, Color.YELLOW); 
  5. mText = typedArray.getString(R.styleable.HoopView_text); 
  6. mCount = typedArray.getString(R.styleable.HoopView_count); 
  7.  
  8. mBgPaint = new Paint(); 
  9. mBgPaint.setAntiAlias(true); 
  10. mBgPaint.setColor(mThemeColor); 
  11. mBgPaint.setAlpha(190); 
  12. mBgPaint.setStyle(Paint.Style.FILL); 
  13.  
  14. mPopPaint = new Paint(); 
  15. mPopPaint.setAntiAlias(true); 
  16. mPopPaint.setColor(Color.LTGRAY); 
  17. mPopPaint.setAlpha(190); 
  18. mPopPaint.setStyle(Paint.Style.FILL_AND_STROKE); 
  19.  
  20. mTextPaint = new TextPaint(); 
  21. mTextPaint.setAntiAlias(true); 
  22. mTextPaint.setColor(mTextColor); 
  23. mTextPaint.setTextSize(context.getResources().getDimension(R.dimen.hoop_text_size)); 
  24.  
  25. mCountTextPaint = new TextPaint(); 
  26. mCountTextPaint.setAntiAlias(true); 
  27. mCountTextPaint.setColor(mThemeColor); 
  28. mCountTextPaint.setTextSize(context.getResources().getDimension(R.dimen.hoop_count_text_size)); 
  29.  
  30. typedArray.recycle(); 
  31.  
  32. mBigRadius = context.getResources().getDimension(R.dimen.hoop_big_circle_radius); 
  33. mSmallRadius = context.getResources().getDimension(R.dimen.hoop_small_circle_radius); 
  34. margin = (int) context.getResources().getDimension(R.dimen.hoop_margin); 
  35. mHeight = (int) context.getResources().getDimension(R.dimen.hoop_view_height); 
  36. countMargin = (int) context.getResources().getDimension(R.dimen.hoop_count_margin); 
  37.  
  38. mDatas = new String[] {"1", "10", "100"}; 
  39. // 计算背景框改变的长度,默认是三个按钮 
  40. mChangeWidth = (int) (2 * mSmallRadius * 3 + 4 * margin);}  

在onMeasure中测出view的宽度后,根据宽度计算出背景圆的圆心坐标和一些相关的数据值。


  1. @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
  2.  
  3. int widthSize = MeasureSpec.getSize(widthMeasureSpec); 
  4.  
  5. mWidth = getDefaultSize(widthSize, widthMeasureSpec); 
  6.  
  7. setMeasuredDimension(mWidth, mHeight); 
  8.  
  9.  
  10. // 此时才测出了mWidth值,再计算圆心坐标及相关值 
  11.  
  12. cx = mWidth - mBigRadius; 
  13.  
  14. cy = mHeight - mBigRadius; 
  15.  
  16. // 大圆圆心 
  17.  
  18. circle = new PointF(cx, cy); 
  19.  
  20. // 三个按钮的圆心 
  21.  
  22. circleOne = new PointF(cx - mBigRadius - mSmallRadius - margin, cy); 
  23.  
  24. circleTwo = new PointF(cx - mBigRadius - 3 * mSmallRadius - 2 * margin, cy); 
  25.  
  26. circleThree = new PointF(cx - mBigRadius - 5 * mSmallRadius - 3 * margin, cy); 
  27.  
  28. // 初始的背景框的边界即为大圆的四个边界点 
  29.  
  30. top = cy - mBigRadius; 
  31.  
  32. bottom = cy + mBigRadius; 
  33.  
  34. }  

因为这里面涉及到点击按钮展开和收缩的过程,所以我定义了如下几种状态,只有在特定的状态下才能进行某些操作。


  1. private int mState = STATE_NORMAL;//当前展开收缩的状态 
  2.  
  3. private boolean mIsRun = false;//是否正在展开或收缩 
  4.  
  5.  
  6. //正常状态 
  7.  
  8. public static final int STATE_NORMAL = 0; 
  9.  
  10. //按钮展开 
  11.  
  12. public static final int STATE_EXPAND = 1; 
  13.  
  14. //按钮收缩 
  15.  
  16. public static final int STATE_SHRINK = 2; 
  17.  
  18. //正在展开 
  19.  
  20. public static final int STATE_EXPANDING = 3; 
  21.  
  22. //正在收缩 
  23.  
  24. public static final int STATE_SHRINKING = 4;  

接下来就执行onDraw方法了,先看看代码:


  1. @Override protected void onDraw(Canvas canvas) { 
  2.  
  3. switch (mState) { 
  4.  
  5. case STATE_NORMAL: 
  6.  
  7. drawCircle(canvas); 
  8.  
  9. break; 
  10.  
  11. case STATE_SHRINK: 
  12.  
  13. case STATE_SHRINKING: 
  14.  
  15. drawBackground(canvas); 
  16.  
  17. break; 
  18.  
  19. case STATE_EXPAND: 
  20.  
  21. case STATE_EXPANDING: 
  22.  
  23. drawBackground(canvas); 
  24.  
  25. break; 
  26.  
  27.  
  28. drawCircleText(canvas); 
  29.  
  30. drawCountText(canvas); 
  31.  
  32. }  

圆上方的数字和圆内的文字是整个过程中一直存在的,所以我将这两个操作放在switch之外,正常状态下绘制圆和之前两部分文字,点击展开时绘制背景框展开过程和文字,展开状态下再次点击绘制收缩过程和文字,当然在绘制背景框的方法中也需要不断绘制大圆,大圆也是一直存在的。

上面的绘制方法:


  1. /** 
  2.  
  3.  * 画背景大圆 
  4.  
  5.  * @param canvas 
  6.  
  7.  */ 
  8.  
  9. private void drawCircle(Canvas canvas) { 
  10.  
  11. left = cx - mBigRadius; 
  12.  
  13. right = cx + mBigRadius; 
  14.  
  15. canvas.drawCircle(cx, cy, mBigRadius, mBgPaint); 
  16.  
  17.  
  18.  
  19.  
  20. /** 
  21.  
  22.  * 画大圆上面表示金币数的文字 
  23.  
  24.  * @param canvas 
  25.  
  26.  */ 
  27.  
  28. private void drawCountText(Canvas canvas) { 
  29.  
  30. canvas.translate(0, -countMargin); 
  31.  
  32. //计算文字的宽度 
  33.  
  34. float textWidth = mCountTextPaint.measureText(mCount, 0, mCount.length()); 
  35.  
  36. canvas.drawText(mCount, 0, mCount.length(), (2 * mBigRadius - textWidth - 35) / 2, 0.2f, mCountTextPaint); 
  37.  
  38.  
  39.  
  40.  
  41. /** 
  42.  
  43.  * 画大圆内的文字 
  44.  
  45.  * @param canvas 
  46.  
  47.  */ 
  48.  
  49. private void drawCircleText(Canvas canvas) { 
  50.  
  51. StaticLayout layout = new StaticLayout(mText, mTextPaint, (int) (mBigRadius * Math.sqrt(2)), Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, true); 
  52.  
  53. canvas.translate(mWidth - mBigRadius * 1.707f, mHeight - mBigRadius * 1.707f); 
  54.  
  55. layout.draw(canvas); 
  56.  
  57. canvas.save(); 
  58.  
  59.  
  60.  
  61.  
  62. /** 
  63.  
  64.  * 画背景框展开和收缩 
  65.  
  66.  * @param canvas 
  67.  
  68.  */ 
  69.  
  70. private void drawBackground(Canvas canvas) { 
  71.  
  72. left = cx - mBigRadius - mChange; 
  73.  
  74. right = cx + mBigRadius; 
  75.  
  76. canvas.drawRoundRect(left, top, right, bottom, mBigRadius, mBigRadius, mPopPaint); 
  77.  
  78. if ((mChange > 0) && (mChange <= 2 * mSmallRadius + margin)) { 
  79.  
  80. // 绘制第一个按钮 
  81.  
  82. canvas.drawCircle(cx - mChange, cy, mSmallRadius, mBgPaint); 
  83.  
  84. // 绘制第一个按钮内的文字 
  85.  
  86. canvas.drawText(mDatas[0], cx - (mBigRadius - mSmallRadius) - mChange, cy + 15, mTextPaint); 
  87.  
  88. } else if ((mChange > 2 * mSmallRadius + margin) && (mChange <= 4 * mSmallRadius + 2 * margin)) { 
  89.  
  90. // 绘制第一个按钮 
  91.  
  92. canvas.drawCircle(cx - mBigRadius - mSmallRadius - margin, cy, mSmallRadius, mBgPaint); 
  93.  
  94. // 绘制第一个按钮内的文字 
  95.  
  96. canvas.drawText(mDatas[0], cx - mBigRadius - mSmallRadius - margin - 20, cy + 15, mTextPaint); 
  97.  
  98.  
  99. // 绘制第二个按钮 
  100.  
  101. canvas.drawCircle(cx - mChange, cy, mSmallRadius, mBgPaint); 
  102.  
  103. // 绘制第二个按钮内的文字 
  104.  
  105. canvas.drawText(mDatas[1], cx - mChange - 20, cy + 15, mTextPaint); 
  106.  
  107. } else if ((mChange > 4 * mSmallRadius + 2 * margin) && (mChange <= 6 * mSmallRadius + 3 * margin)) { 
  108.  
  109. // 绘制第一个按钮 
  110.  
  111. canvas.drawCircle(cx - mBigRadius - mSmallRadius - margin, cy, mSmallRadius, mBgPaint); 
  112.  
  113. // 绘制第一个按钮内的文字 
  114.  
  115. canvas.drawText(mDatas[0], cx - mBigRadius - mSmallRadius - margin - 16, cy + 15, mTextPaint); 
  116.  
  117.  
  118. // 绘制第二个按钮 
  119.  
  120. canvas.drawCircle(cx - mBigRadius - 3 * mSmallRadius - 2 * margin, cy, mSmallRadius, mBgPaint); 
  121.  
  122. // 绘制第二个按钮内的文字 
  123.  
  124. canvas.drawText(mDatas[1], cx - mBigRadius - 3 * mSmallRadius - 2 * margin - 25, cy + 15, mTextPaint); 
  125.  
  126.  
  127. // 绘制第三个按钮 
  128.  
  129. canvas.drawCircle(cx - mChange, cy, mSmallRadius, mBgPaint); 
  130.  
  131. // 绘制第三个按钮内的文字 
  132.  
  133. canvas.drawText(mDatas[2], cx - mChange - 34, cy + 15, mTextPaint); 
  134.  
  135. } else  if (mChange > 6 * mSmallRadius + 3 * margin) { 
  136.  
  137. // 绘制第一个按钮 
  138.  
  139. canvas.drawCircle(cx - mBigRadius - mSmallRadius - margin, cy, mSmallRadius, mBgPaint); 
  140.  
  141. // 绘制第一个按钮内的文字 
  142.  
  143. canvas.drawText(mDatas[0], cx - mBigRadius - mSmallRadius - margin - 16, cy + 15, mTextPaint); 
  144.  
  145.  
  146. // 绘制第二个按钮 
  147.  
  148. canvas.drawCircle(cx - mBigRadius - 3 * mSmallRadius - 2 * margin, cy, mSmallRadius, mBgPaint); 
  149.  
  150. // 绘制第二个按钮内的文字 
  151.  
  152. canvas.drawText(mDatas[1], cx - mBigRadius - 3 * mSmallRadius - 2 * margin - 25, cy + 15, mTextPaint); 
  153.  
  154.  
  155. // 绘制第三个按钮 
  156.  
  157. canvas.drawCircle(cx - mBigRadius - 5 * mSmallRadius - 3 * margin, cy, mSmallRadius, mBgPaint); 
  158.  
  159. // 绘制第三个按钮内的文字 
  160.  
  161. canvas.drawText(mDatas[2], cx - mBigRadius - 5 * mSmallRadius - 3 * margin - 34, cy + 15, mTextPaint); 
  162.  
  163.  
  164. drawCircle(canvas); 
  165.  
  166.  
  167. }  

然后是点击事件的处理,只有触摸点在大圆内时才会触发展开或收缩的操作,点击小圆时提供了一个接口给外部调用。


  1. @Override public boolean onTouchEvent(MotionEvent event) { 
  2.  
  3. int action = event.getAction(); 
  4.  
  5. switch (action) { 
  6.  
  7. case MotionEvent.ACTION_DOWN: 
  8.  
  9. //如果点击的时候动画在进行,不处理 
  10.  
  11. if (mIsRun) return true; 
  12.  
  13. PointF pointF = new PointF(event.getX(), event.getY()); 
  14.  
  15. if (isPointInCircle(pointF, circle, mBigRadius)) { //如果触摸点在大圆内,根据弹出方向弹出或者收缩按钮 
  16.  
  17. if ((mState == STATE_SHRINK || mState == STATE_NORMAL) && !mIsRun) { 
  18.  
  19. //展开 
  20.  
  21. mIsRun = true;//这是必须先设置true,因为onAnimationStart在onAnimationUpdate之后才调用 
  22.  
  23. showPopMenu(); 
  24.  
  25. } else { 
  26.  
  27. //收缩 
  28.  
  29. mIsRun = true; 
  30.  
  31. hidePopMenu(); 
  32.  
  33.  
  34. } else { //触摸点不在大圆内 
  35.  
  36. if (mState == STATE_EXPAND) { //如果是展开状态 
  37.  
  38. if (isPointInCircle(pointF, circleOne, mSmallRadius)) { 
  39.  
  40. listener.clickButton(this, Integer.parseInt(mDatas[0])); 
  41.  
  42. } else if (isPointInCircle(pointF, circleTwo, mSmallRadius)) { 
  43.  
  44. listener.clickButton(this, Integer.parseInt(mDatas[1])); 
  45.  
  46. } else if (isPointInCircle(pointF, circleThree, mSmallRadius)) { 
  47.  
  48. listener.clickButton(this, Integer.parseInt(mDatas[2])); 
  49.  
  50.  
  51. mIsRun = true; 
  52.  
  53. hidePopMenu(); 
  54.  
  55.  
  56.  
  57. break; 
  58.  
  59.  
  60. return super.onTouchEvent(event); 
  61.  
  62. }  

展开和收缩的动画是改变背景框的宽度属性的动画,并监听这个属性动画,在宽度值改变的过程中去重新绘制整个view。因为一开始我就确定了大圆小圆的半径和小圆与背景框之间的间距,所以初始化时已经计算好了背景框的宽度:


  1. mChangeWidth = (int) (2 * mSmallRadius * 3 + 4 * margin);  

  1. /** 
  2.  
  3.  * 弹出背景框 
  4.  
  5.  */ 
  6.  
  7. private void showPopMenu() { 
  8.  
  9. if (mState == STATE_SHRINK || mState == STATE_NORMAL) { 
  10.  
  11. ValueAnimator animator = ValueAnimator.ofInt(0, mChangeWidth); 
  12.  
  13. animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
  14.  
  15. @Override public void onAnimationUpdate(ValueAnimator animation) { 
  16.  
  17. if (mIsRun) { 
  18.  
  19. mChange = (int) animation.getAnimatedValue(); 
  20.  
  21. invalidate(); 
  22.  
  23. } else { 
  24.  
  25. animation.cancel(); 
  26.  
  27. mState = STATE_NORMAL; 
  28.  
  29.  
  30.  
  31. }); 
  32.  
  33. animator.addListener(new AnimatorListenerAdapter() { 
  34.  
  35. @Override public void onAnimationStart(Animator animation) { 
  36.  
  37. super.onAnimationStart(animation); 
  38.  
  39. mIsRun = true; 
  40.  
  41. mState = STATE_EXPANDING; 
  42.  
  43.  
  44.  
  45.  
  46. @Override public void onAnimationCancel(Animator animation) { 
  47.  
  48. super.onAnimationCancel(animation); 
  49.  
  50. mIsRun = false; 
  51.  
  52. mState = STATE_NORMAL; 
  53.  
  54.  
  55.  
  56.  
  57. @Override public void onAnimationEnd(Animator animation) { 
  58.  
  59. super.onAnimationEnd(animation); 
  60.  
  61. mIsRun = false; 
  62.  
  63. //动画结束后设置状态为展开 
  64.  
  65. mState = STATE_EXPAND; 
  66.  
  67.  
  68. }); 
  69.  
  70. animator.setDuration(500); 
  71.  
  72. animator.start(); 
  73.  
  74.  
  75. }   

  1. /** 
  2.  
  3.  * 隐藏弹出框 
  4.  
  5.  */ 
  6.  
  7. private void hidePopMenu() { 
  8.  
  9. if (mState == STATE_EXPAND) { 
  10.  
  11. ValueAnimator animator = ValueAnimator.ofInt(mChangeWidth, 0); 
  12.  
  13. animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
  14.  
  15. @Override public void onAnimationUpdate(ValueAnimator animation) { 
  16.  
  17. if (mIsRun) { 
  18.  
  19. mChange = (int) animation.getAnimatedValue(); 
  20.  
  21. invalidate(); 
  22.  
  23. } else { 
  24.  
  25. animation.cancel(); 
  26.  
  27.  
  28.  
  29. }); 
  30.  
  31. animator.addListener(new AnimatorListenerAdapter() { 
  32.  
  33. @Override public void onAnimationStart(Animator animation) { 
  34.  
  35. super.onAnimationStart(animation); 
  36.  
  37. mIsRun = true; 
  38.  
  39. mState = STATE_SHRINKING; 
  40.  
  41.  
  42.  
  43.  
  44. @Override public void onAnimationCancel(Animator animation) { 
  45.  
  46. super.onAnimationCancel(animation); 
  47.  
  48. mIsRun = false; 
  49.  
  50. mState = STATE_EXPAND; 
  51.  
  52.  
  53.  
  54.  
  55. @Override public void onAnimationEnd(Animator animation) { 
  56.  
  57. super.onAnimationEnd(animation); 
  58.  
  59. mIsRun = false; 
  60.  
  61. //动画结束后设置状态为收缩 
  62.  
  63. mState = STATE_SHRINK; 
  64.  
  65.  
  66. }); 
  67.  
  68. animator.setDuration(500); 
  69.  
  70. animator.start(); 
  71.  
  72.  
  73. }  

这个过程看起来是弹出或收缩,实际上宽度值每改变一点,就将所有的组件重绘一次,只是文字和大圆等内容的尺寸及位置都没有变化,只有背景框的宽度值在变,所以才有这种效果。

在xml中的使用:


  1. <LinearLayout 
  2.  
  3. android:layout_width="match_parent" 
  4.  
  5. android:layout_height="wrap_content" 
  6.  
  7. android:layout_alignParentBottom="true" 
  8.  
  9. android:layout_marginBottom="20dp" 
  10.  
  11. android:layout_alignParentRight="true" 
  12.  
  13. android:orientation="vertical"> 
  14.  
  15.  
  16. <com.xx.hoopcustomview.HoopView 
  17.  
  18. android:id="@+id/hoopview1" 
  19.  
  20. android:layout_width="match_parent" 
  21.  
  22. android:layout_height="wrap_content" 
  23.  
  24. android:layout_marginRight="10dp" 
  25.  
  26. app:text="支持火箭" 
  27.  
  28. app:count="1358" 
  29.  
  30. app:theme_color="#31A129"/> 
  31.  
  32.  
  33. <com.xx.hoopcustomview.HoopView 
  34.  
  35. android:id="@+id/hoopview2" 
  36.  
  37. android:layout_width="match_parent" 
  38.  
  39. android:layout_height="wrap_content" 
  40.  
  41. android:layout_marginRight="10dp" 
  42.  
  43. app:text="热火无敌" 
  44.  
  45. app:count="251" 
  46.  
  47. app:theme_color="#F49C11"/> 
  48.  
  49. </LinearLayout>  

activity中使用:


  1. hoopview1 = (HoopView) findViewById(R.id.hoopview1); 
  2.  
  3. hoopview1.setOnClickButtonListener(new HoopView.OnClickButtonListener() { 
  4.  
  5. @Override public void clickButton(View view, int num) { 
  6.  
  7. Toast.makeText(MainActivity.this, "hoopview1增加了" + num, Toast.LENGTH_SHORT).show(); 
  8.  
  9.  
  10. });  

大致实现过程就是这样,与原始效果还是有点区别,我这个还有很多瑕疵,比如文字的位置居中问题,弹出或收缩时,小圆内的文字的旋转动画我没有实现。

作者:shenhuniurou

来源:51CTO

时间: 2024-10-03 14:15:53

自定义View-仿虎扑直播比赛界面的打赏按钮的相关文章

Android自定义View模仿虎扑直播界面的打赏按钮功能

前言 作为一个资深篮球爱好者,我经常会用虎扑app看比赛直播,后来注意到文字直播界面右下角加了两个按钮,可以在直播过程中送虎扑币,为自己支持的球队加油. 具体的效果如下图所示: 我个人觉得挺好玩的,所以决定自己实现下这个按钮,废话不多说,先看实现的效果吧: 这个效果看起来和popupwindow差不多,但我是采用自定义view的方式来实现,下面说说过程. 实现过程 首先从虎扑的效果可以看到,它这两个按钮时浮在整个界面之上的,所以它需要和FrameLayout结合使用,因此我让它的宽度跟随屏幕大小

Android自定义View仿支付宝输入六位密码功能_Android

跟选择银行卡界面类似,也是用一个PopupWindow,不过输入密码界面是一个自定义view,当输入六位密码完成后用回调在Activity中获取到输入的密码并以Toast显示密码.效果图如下: 自定义view布局效果图及代码如下: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/

Android自定义View仿支付宝输入六位密码功能

跟选择银行卡界面类似,也是用一个PopupWindow,不过输入密码界面是一个自定义view,当输入六位密码完成后用回调在Activity中获取到输入的密码并以Toast显示密码.效果图如下: 自定义view布局效果图及代码如下: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/

自定义View仿TabHost的实现

Activity1如下: [java] view plaincopy package cn.com;   import android.app.Activity;   import android.os.Bundle;   public class Activity1 extends Activity{        @Override       protected void onCreate(Bundle savedInstanceState) {           super.onCre

自定义View仿TabHost的实现(一)

Activity1如下: package cn.com; import android.app.Activity; import android.os.Bundle; public class Activity1 extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //设置setTitle应该在setTitle之前

Android自定义View仿IOS圆盘时间选择器

通过自定义view实现仿iOS实现滑动两端的点选择时间的效果 效果图 自定义的view代码 public class Ring_Slide2 extends View { private static final double RADIAN = 180 / Math.PI; private int max_progress; // 设置最大进度 private int cur_progress; //设置锚点1当前进度 private int cur_progress2; //设置锚点2进度 p

Android自定义View 仿QQ侧滑菜单的实现代码

先看看QQ的侧滑效果 分析一下 先上原理图(不知道能否表达的清楚 ==) -首先这里使用了 Android 的HorizontalScrollView 水平滑动布局作为容器,当然我们需要继承它自定义一个侧滑视图 - 这个容器里面有一个父布局(一般用LinerLayout,本demo用的是),这个父布局里面有且只有两个子控件(布局),初始状态菜单页的位置在Y轴上存在偏移这样可以就可以形成主页叠在菜单页的上方的视觉效果:然后在滑动的过程程中 逐渐修正偏移,最后菜单页和主页并排排列.原理搞清了实现起来

Android自定义View仿QQ健康界面_Android

最近一直在学习自定义View相关的知识,今天给大家带来的是QQ健康界面的实现.先看效果图: 可以设置数字颜色,字体颜色,运动步数,运动排名,运动平均步数,虚线下方的蓝色指示条的长度会随着平均步数改变而进行变化.整体效果还是和QQ运动健康界面很像的. 自定义View四部曲,一起来看看怎么实现的. 1.自定义view的属性: <?xml version="1.0" encoding="utf-8"?> <resources> //自定义属性名,定

Android自定义View仿QQ健康界面

最近一直在学习自定义View相关的知识,今天给大家带来的是QQ健康界面的实现.先看效果图: 可以设置数字颜色,字体颜色,运动步数,运动排名,运动平均步数,虚线下方的蓝色指示条的长度会随着平均步数改变而进行变化.整体效果还是和QQ运动健康界面很像的. 自定义View四部曲,一起来看看怎么实现的. 1.自定义view的属性: <?xml version="1.0" encoding="utf-8"?> <resources> //自定义属性名,定