Android自定义View——拼手气拼盘

效果图

这里写图片描述

原理分析

这里的转盘主要实现的重点是绘制每块答案区域的文本,并绘制出来,而转盘和背景只是张图片

1、绘制文本的位置区域
2、获取旋转动画
3、提供接口

实现步骤

1、初始化变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//文本相关
private List<String> mRollGameTextList;
private int mRollGameTextCount = 0;
private Paint mTextPaint;
private int mTextSize = 14;
private RectF mRange; //View的区域
private int mRadius; //View的直径
private int mWidth; //View的宽度
private float mStartAngle = 22.5f; //开始的角度
//旋转动画相关
private boolean isRolling = false;
private float fromDegress = 0;
private float toDegress = 0;
//拼手气的正确答案位置
private int mAnswerPosition = 0;
//拼手气转盘转一圈的时间
private int mTimeForPer = 800;
//拼手气转盘的圈数
private int mRollCount = 3;
//拼手气转盘第一行文本的个数
private int mFirstLineTextCount = 4;

2、测量大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public RollGameTextViewList(Context context) {
this(context, null);
}
public RollGameTextViewList(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RollGameTextViewList(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mTextPaint = new Paint();
mTextPaint.setColor(Color.parseColor("#611E14"));
mTextPaint.setTextSize(dip2px(context, mTextSize));
}
/**
* 设置为正方形
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = Math.min(getMeasuredWidth(), getMeasuredHeight());
mRadius = mWidth - getPaddingLeft() - getPaddingRight();
setMeasuredDimension(mWidth, mWidth);
mRange = new RectF(getPaddingLeft(), getPaddingTop(), mRadius
+ getPaddingLeft(), mRadius + getPaddingTop());
}

3、绘制文本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@Override
protected void onDraw(Canvas canvas) {
if (checkStartCondition()) {
canvas.save();
canvas.rotate(-90, mWidth / 2, mWidth / 2);
float tmpAngle = mStartAngle;
float sweepAngle = (float) (360 / mRollGameTextCount);
for (int i = 0; i < mRollGameTextList.size(); i++) {
drawText(canvas, tmpAngle, sweepAngle, mRollGameTextList.get(i), i);
tmpAngle += sweepAngle;
}
canvas.restore();
}
}
private void drawText(Canvas canvas, float startAngle, float sweepAngle, String text, int position) {
if (text.length() >= mFirstLineTextCount) {
drawLineTwo(canvas, startAngle, sweepAngle, text, position);
} else {
drawLineOne(canvas, startAngle, sweepAngle, text, position);
}
}
/**
* 兼容两行文本
*
* @param canvas
* @param startAngle
* @param sweepAngle
* @param text
*/
private void drawLineTwo(Canvas canvas, float startAngle, float sweepAngle, String text, int position) {
String textForWard = text.substring(0, mFirstLineTextCount);
String textForBack = text.substring(mFirstLineTextCount, text.length());
int textWidthForWard = measureTextView(textForWard).width();
int textHeightForWard = measureTextView(textForWard).height();
int textWidthForBack = measureTextView(textForBack).width();
int textHeightForBack = measureTextView(textForBack).height();
float h1_Offset = (float) (mRadius * Math.PI / mRollGameTextCount / 2 - textWidthForWard / 2);// 水平偏移:圆长/个数/2 - 文本宽度/2
float v1_Offset = textHeightForWard;// 垂直偏移
float h2_Offset = (float) (mRadius * Math.PI / mRollGameTextCount / 2 - textWidthForBack / 2);// 水平偏移:圆长/个数/2 - 文本宽度/2
float v2_Offset = textHeightForWard + textHeightForBack;// 垂直偏移
Path path = new Path();
path.addArc(mRange, startAngle, sweepAngle);
canvas.drawTextOnPath(textForWard, path, h1_Offset, v1_Offset, mTextPaint);
canvas.drawTextOnPath(textForBack, path, h2_Offset, v2_Offset, mTextPaint);
}
/**
* 兼容一行文本
*
* @param canvas
* @param startAngle
* @param sweepAngle
* @param text
*/
private void drawLineOne(Canvas canvas, float startAngle, float sweepAngle, String text, int position) {
Path path = new Path();
path.addArc(mRange, startAngle, sweepAngle);
int textWidth = measureTextView(text).width();
int textHeight = measureTextView(text).height();
float hOffset = (float) (mRadius * Math.PI / mRollGameTextCount / 2 - textWidth / 2);// 水平偏移:圆长/个数/2 - 文本宽度/2
float vOffset = textHeight;// 垂直偏移
canvas.drawTextOnPath(text, path, hOffset, vOffset, mTextPaint);
}
/**
* 测量文本
*
* @param text
* @return
*/
private Rect measureTextView(String text) {
Rect bounds = new Rect();
mTextPaint.getTextBounds(text, 0, text.length(), bounds);
return bounds;
}

4、旋转动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
* 开启拼手气
*/
private void startRollGame() {
if (checkStartCondition()) {
if (!isRolling) {
isRolling = true;
toDegress = mAnswerPosition * 45 + mRollCount * 360;
startRollGameRotateAnimation(fromDegress, toDegress);
fromDegress = fromDegress + (toDegress - fromDegress) % 360;
Log.e("TAG", "fromDegress:" + fromDegress + "-toDegress:" + toDegress + "-" + mAnswerPosition);
}
}
}
/**
* 判断开启条件
*
* @return
*/
private boolean checkStartCondition() {
if (null == mRollGameTextList || mRollGameTextList.size() == 0 || mRollCount == 0) {
return false;
}
return true;
}
/**
* 开启旋转动画
*
* @param fromDegress
* @param toDegress
*/
private void startRollGameRotateAnimation(float fromDegress, float toDegress) {
RotateAnimation rollGameRotateAnimation = getRollGameRotateAnimation(fromDegress, toDegress);
startAnimation(rollGameRotateAnimation);
}
/**
* 获取旋转动画
*
* @param fromDegress
* @param toDegress
* @return
*/
private RotateAnimation getRollGameRotateAnimation(float fromDegress, float toDegress) {
RotateAnimation rotateAnimation = new RotateAnimation(fromDegress, toDegress, mWidth / 2, mWidth / 2);
rotateAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
rotateAnimation.setDuration((long) ((toDegress - fromDegress) / 360 * mTimeForPer));
rotateAnimation.setAnimationListener(rollGameAnimListener);
rotateAnimation.setFillAfter(true);
return rotateAnimation;
}
/**
* 动画监听
*/
private Animation.AnimationListener rollGameAnimListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
isRolling = false;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
};

5、提供API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public void setAnswerPosition(int mAnswerPosition) {
this.mAnswerPosition = mAnswerPosition;
}
public void setTimeForPer(int mTimeForPer) {
this.mTimeForPer = mTimeForPer;
}
public void setRollCount(int mRollCount) {
this.mRollCount = mRollCount;
}
public void setFirstLineTextCount(int mFirstLineTextCount) {
this.mFirstLineTextCount = mFirstLineTextCount;
}
public void setRollGameTextList(List<String> mRollGameTextList) {
if (null != mRollGameTextList && mRollGameTextList.size() > 0) {
this.mRollGameTextList = mRollGameTextList;
this.mRollGameTextCount = mRollGameTextList.size();
Collections.reverse(mRollGameTextList);
invalidate();
}
}
/**
* 开启动画
*/
public void start() {
startRollGame();
}

6、转盘的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MainActivity extends AppCompatActivity{
private RollGameTextViewList iv_rollgame_context;
private static List<String> text = new ArrayList<>();
static {
text.add("送");
text.add("送礼");
text.add("送礼物");
text.add("送礼物吧");
text.add("送礼物微信");
text.add("送礼物我爱你");
text.add("送礼物宝贵砖石");
text.add("送礼物我爱你么么");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv_rollgame_context = findViewById(R.id.iv_rollgame_context);
iv_rollgame_context.setRollCount(10);
iv_rollgame_context.setTimeForPer(800);
iv_rollgame_context.setAnswerPosition(2);
iv_rollgame_context.setFirstLineTextCount(4);
iv_rollgame_context.setRollGameTextList(text);
}
public void startRoll(View view) {
iv_rollgame_context.start();
}
}

其布局的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
tools:context="com.yy.a.MainActivity">
<ImageView
android:layout_width="284dp"
android:layout_height="284dp"
android:layout_centerInParent="true"
android:background="@drawable/rg_zp_bg" />
<com.yy.a.RollGameTextViewList
android:id="@+id/iv_rollgame_context"
android:layout_width="235dp"
android:layout_height="235dp"
android:layout_centerInParent="true"
android:background="@drawable/rg_zp_context"
android:padding="10dp" />
<Button
android:layout_width="75dp"
android:layout_height="75dp"
android:layout_centerInParent="true"
android:background="@drawable/rg_start_off_btn"
android:onClick="startRoll"
android:text="启动" />
</RelativeLayout>

7、源码下载

源码下载

8、点击事件

转盘的每块区域增加点击事件,具体的代码不便放在源码,其主要步骤如下

  1. 获取每块盘块的区域Path
  2. 创建Region集合,添加盘块的Path
  3. 复写onTouchEvent方法
  4. 通过遍历Region跟点击的x,y比较,判断是否在当前区域
  5. 提供接口,进行回调
坚持原创技术分享,您的支持将鼓励我继续创作!