腾讯的狼人杀。玩家发言在矩形的头相框,动态展示倒计时进度条,感觉很好玩,参考网上一些做法:

最终可以实现效果:

Paste_Image.png

可以控制 修改颜色显示 ,是否显示小球等:

先给出demo地址:

主界面大体上就是通过handler 发送消息更新ui的思想,布局也比较简单。

重点是绘制:

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.drawable.shapes.RectShape;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
* Created by Administrator on 2017/8/26.
*/
public class SquareProgress extends View {
private String TAG = "SquareProgress";
//各个画笔的颜色
private int maxColor = Color.YELLOW;//总进度条颜色为灰色
private int curColor = Color.GREEN;//当前进度条颜色为蓝色
private int dotColor = Color.RED;//进度条前端的小圆点为红色
private float allLength;//进度条的总长度
private int maxProgress = 60;//总的进度条长度为100(可改变)
private int curProgress = 0;//当前进度为30(可改变)
private Paint curPaint;//当前进度条的画笔
private Paint maxPaint;//总进度条的画笔
private Paint dotPaint;//进度条前端小圆点的画笔
private int width;//整个view的宽度,(包括paddingleft和paddingright)
private int height;//整个view的高度,(包括paddingtop和paddingbottom)
private float maxProgressWidth;//整个进度条画笔的宽度
private float curProgressWidth;//当前进度条画笔的宽度
private float dotDiameter;//进度条顶端小圆点的直径
private boolean canDisplayDot = true;//是否显示小圆点
private Path curPath;//当前进度条的路径,(总的进度条的路径作为onDraw的局部变量)
private float proWidth;//整个进度条构成矩形的宽度
private float proHeight;//整个进度条构成矩形的高度
private float dotCX;//小圆点的X坐标(相对view)
private float dotCY;//小圆点的Y坐标(相对view)
public SquareProgress(Context context) {
super(context);
initView();
}
public SquareProgress(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public SquareProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
@TargetApi(21)
public SquareProgress(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initView();
}
//
private void initView() {
canDisplayDot = true;//默认能显示小圆点
curPaint = new Paint();//当前进度条的画笔设置
curProgressWidth = dp2Px(5);//dp转px
curPaint.setAntiAlias(true);//设置画笔抗锯齿
curPaint.setStyle(Paint.Style.STROKE);//设置画笔(忘了)
curPaint.setStrokeWidth(curProgressWidth);//设置画笔宽度
curPaint.setColor(curColor);//设置画笔颜色
maxProgressWidth = dp2Px(5);//总的进度条的画笔设置
maxPaint = new Paint();
maxPaint.setAntiAlias(true);
maxPaint.setColor(maxColor);
maxPaint.setStyle(Paint.Style.STROKE);
maxPaint.setStrokeWidth(maxProgressWidth);
dotPaint = new Paint();//小圆点的画笔设置
dotDiameter = dp2Px(20);
dotPaint.setAntiAlias(true);
dotPaint.setStyle(Paint.Style.FILL);//因为是画圆,所以这里是这种模式
dotPaint.setColor(dotColor);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = measureWidth(widthMeasureSpec);//得到view的宽度
height = measureHeight(heightMeasureSpec);//得到view的高度
setMeasuredDimension(width, height);//将自己重新测量的宽高度应用到视图上(只设置size而不设置mode,mode是在布局中就确定了的)
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int tWidth = width - getPaddingRight() - getPaddingLeft();//得到整个view出去padding后的宽度
int tHeight = height - getPaddingTop() - getPaddingBottom();//得到整个view除去padding后的高度
int point1X = getPaddingLeft() + tWidth / 10;//得到第一个点的X坐标(相对于view)
int point1Y = getPaddingTop() + tHeight / 10;
int point2X = tWidth + getPaddingLeft() - tWidth / 10;
int point2Y = getPaddingTop() + tHeight / 10;
int point3X = tWidth + getPaddingLeft() - tWidth / 10;
int point3Y = tHeight + getPaddingTop() - tHeight / 10;
int point4X = getPaddingLeft() + tWidth / 10;
int point4Y = tHeight + getPaddingTop() - tHeight / 10;
// Log.i(TAG, "onDraw: point1:" + point1X + "," + point1Y);
// Log.i(TAG, "onDraw: point2:" + point2X + "," + point2Y);
// Log.i(TAG, "onDraw: point3:" + point3X + "," + point3Y);
// Log.i(TAG, "onDraw: point4:" + point4X + "," + point4Y);
proWidth = point3X - point1X;
proHeight = point3Y - point1Y;
Log.i(TAG, "onDraw: point5:" + proWidth + "," + proHeight);
Path maxpath = new Path();//整个进度条的路径
maxpath.moveTo(point1X, point1Y);
maxpath.lineTo(point2X, point2Y);
maxpath.lineTo(point3X, point3Y);
maxpath.lineTo(point4X, point4Y);
maxpath.close();
canvas.drawPath(maxpath, maxPaint);
allLength = 2 * (proWidth + proHeight);
curPath = new Path();//当前进度条的路径
curPath.moveTo(point1X, point1Y);
float curPersent = (float) curProgress / maxProgress;//当前进度占总进度的百分比
if (curPersent > 0) {
if (curPersent < proWidth / allLength) {//处在第一段上面的小圆点的原点坐标和当前进度条的路径
dotCX = point1X + allLength * curProgress / maxProgress;
dotCY = point1Y;
curPath.lineTo(dotCX, dotCY);
} else if (curPersent < (proHeight + proWidth) / allLength) {
dotCX = point2X;
dotCY = point1Y + allLength * curProgress / maxProgress - proWidth;
curPath.lineTo(point2X, point2Y);
curPath.lineTo(dotCX, dotCY);
} else if (curPersent < (2 * proWidth + proHeight) / allLength) {
dotCX = point1X + allLength - proHeight - allLength * curProgress / maxProgress;
dotCY = point4Y;
curPath.lineTo(point2X, point2Y);
curPath.lineTo(point3X, point3Y);
curPath.lineTo(dotCX, dotCY);
} else if (curPersent < 1) {
dotCX = point1X;
dotCY = point1Y + allLength - allLength * curProgress / maxProgress;
curPath.lineTo(point2X, point2Y);
curPath.lineTo(point3X, point3Y);
curPath.lineTo(point4X, point4Y);
curPath.lineTo(dotCX, dotCY);
} else if (curPersent > 1) {
dotCX = point1X;
dotCY = point1Y;
curPath.lineTo(point2X, point2Y);
curPath.lineTo(point3X, point3Y);
curPath.lineTo(point4X, point4Y);
curPath.close();
}
} else {
dotCX = point1X;
dotCY = point1Y;
curPath.lineTo(point1X, point1Y);
}
Log.i(TAG, "onDraw: dotC:" + dotCX + "," + dotCY);
canvas.drawPath(curPath, curPaint);
if (canDisplayDot) {
canvas.drawCircle(dotCX, dotCY, dotDiameter * 0.6f, dotPaint);
}
}
private int measureWidth(int widthMeasureSpec) {
int result;
int mode = MeasureSpec.getMode(widthMeasureSpec);//得到measurespec的模式
int size = MeasureSpec.getSize(widthMeasureSpec);//得到measurespec的大小
int padding = getPaddingLeft() + getPaddingRight();//得到padding在宽度上的大小
if (mode == MeasureSpec.EXACTLY)//这种模式对应于match_parent和具体的数值dp
{
result = size;
} else {
result = getSuggestedMinimumWidth();//得到屏幕能给的最大的view的最小宽度,原话:Returns the suggested minimum width that the view should use. This returns the maximum of the view's minimum width and the background's minimum width
result += padding;//考虑padding后最大的view最小宽度
if (mode == MeasureSpec.AT_MOST)//这种模式对应于wrap_parent
{
result = Math.max(result, size);
}
}
return result;
}
public void setCurProgress(int curProgress) {
this.curProgress = curProgress;
invalidate();
}
private int measureHeight(int heightMeasureSpec) {
int result;
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
int padding = getPaddingBottom() + getPaddingTop();
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = getSuggestedMinimumHeight();
result += padding;
if (mode == MeasureSpec.AT_MOST) {
result = Math.max(result, size);
}
}
return result;
}
/**
* 数据转换: dp---->px
*/
private float dp2Px(float dp) {
return dp * getContext().getResources().getDisplayMetrics().density;
}

参考多人的,注释也是十分到位,Activity的布局十分简单,上边可以设定进度,下边是进度条展示,下边的中间是当前进度的文本展示。

xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_width="match_parent"
android:layout_height="200dp"
android:orientation="vertical"
android:padding="40dp">
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number" />
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Confirm" />
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
android:id="@+id/sp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text=" " />

对应的 Java 代码也十分简单。

public class MainActivity extends Activity {
private EditText mEditText;
private Button mButton;
TextView tv_time;
private SquareProgress mSquareProgress;
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
tv_time.setText(mProgress + "");
mSquareProgress.setCurProgress(mProgress);
}
};
private int mProgress = 60;//倒计时进度
private Thread mytimeehead;//倒计时显示线程
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mEditText = (EditText) findViewById(R.id.edit);
tv_time = (TextView) findViewById(R.id.tv_time);
mButton = (Button) findViewById(R.id.button1);
mSquareProgress = (SquareProgress) findViewById(R.id.sp);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String sData = mEditText.getText().toString();
int iData = Integer.valueOf(sData);
mSquareProgress.setCurProgress(iData);
mProgress = iData;
}
});
mytimeehead = new Thread() {
@Override
public void run() {
while (mProgress > 0) {
mProgress = mProgress - 1;
//子线程给主线程发送消息更新UI
handler.sendEmptyMessage(0);
SystemClock.sleep(1000);
}
}
};
mytimeehead.start();
}
}

private SquareProgress mSquareProgress; 声明findViewById之后,可以调用 mSquareProgress.setCurProgress(int i);来更新进度。

}