Android View : 自定义方形 进度条

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

最终可以实现效果:


Paste_Image.png

可以控制 修改颜色显示 ,是否显示小球等:
先给出demo地址:
链接: https://pan.baidu.com/s/1bp8NbGz 密码: 75sg

主界面大体上就是通过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的布局十分简单,上边可以设定进度,下边是进度条展示,下边的中间是当前进度的文本展示。

<?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">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:orientation="vertical"
    android:padding="40dp">

    <EditText
        android:id="@+id/edit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="number" />

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Confirm" />
</LinearLayout>

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1">

    <com.myapp.squareprogress.SquareProgress
        android:id="@+id/sp"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <TextView
        android:id="@+id/tv_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text=" " />


</RelativeLayout>

</LinearLayout>

对应的 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);来更新进度。

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,613评论 4 59
  • 内容抽屉菜单ListViewWebViewSwitchButton按钮点赞按钮进度条TabLayout图标下拉刷新...
    皇小弟阅读 46,416评论 22 663
  • 文/柠檬猫 在知乎上看到一个话题:“如果借网贷的人死了,那这笔钱是不是不用还了?”楼主似乎在网贷平台上借了不少钱,...
    与春归阅读 371评论 0 1
  • 昨夜风雨小重楼,檐角雨断风不休。 一地落红一地愁,漫天梧桐满天秋。 花飞不似花开日,雁去雁来还依旧。 无限秋风无限...
    酒肆邻家阅读 220评论 0 0