Android自定义view之下载控件,ProgressBar

编程入门 行业动态 更新时间:2024-10-24 18:15:55

Android<a href=https://www.elefans.com/category/jswz/34/1771438.html style=自定义view之下载控件,ProgressBar"/>

Android自定义view之下载控件,ProgressBar


请尊重别人的劳动成果,转发文章请注明出处


概述


在开发过程中我们总会遇到一些不同于安卓自带的控件,业内称之为自定义控件,一直没有深入了解自定义VIEW,总觉得好像很厉害的样子,最近公司业务需求(做一个APK文件的下载)需要个性化的展示下载进度条。于是尝试着写一个下载进度条的自定义控件

为了不浪费大家的时间,先上效果图,对于赶时间的哥们来说在这里就是一个分水岭了,如果大家奔着学习自定义控件来的,那你不妨接着看下去




效果如图所示,只是小白不会制作动态图,只能随机截取一张示例


自定义VIEW


在网上看了一些博客,这里抛砖引玉,先总结下自定义View的步骤:

1、自定义View的属性

2、在View的构造方法中获得我们自定义的属性

3、#重写onMesure #

4、重写onDraw

第三点使用了不同的符号,想必有特殊的地方,别急,等一下会解释。现在结合我们的需求:下载进度条  最简单的进度条无非两个部分组成

  • 未下载部分
  • 已下载部分

这下简单了,我们使用两种不同的颜色来绘制 已下载部分 和 未下载部分 不就可以了吗?然后再根据下载进度实时重绘已经下载部分 。但是仔细想想一个完整的控件不应该只有两种不同的颜色,而是需要具备:
  • 下载状态文字
  • 文字颜色
  • 文字大小
  • 未下载部分颜色
  • 已经下载部分颜色
 基本的就这几个,但是再仔细想想,我们在应用商店上面看到的下载控件不仅仅就一个状态吧,而且这个下载的进度条要什么形状的呢?所以又有了下面的需求:
  • 矩形进度条  /  圆角矩形进度条
  • 控件其他状态的默认颜色


基本的需求已经分析完了,接下来就按照这自定义VIEW的步骤走吧 1、自定义View的属性,首先在res/values/  下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。
<?xml version="1.0" encoding="utf-8"?>
<resources><!-- 四周圆弧度 --><attr name="cornerRadius" format="dimension" /><attr name="text" format="string" /><attr name="textColor" format="color" /><attr name="textSize" format="dimension" /><!-- 默认颜色 --><attr name="defaultColor" format="color" /><!-- 未下载部分颜色 --><attr name="undownloadColor" format="color" /><!-- 已经下载部分颜色 --><attr name="downloadedColor" format="color" /><!-- RuffianProgressBarLine --><declare-styleable name="RuffianProgressBarLine"><attr name="cornerRadius" /><attr name="text" /><attr name="textColor" /><attr name="textSize" /><attr name="defaultColor" /><attr name="undownloadColor" /><attr name="downloadedColor" /></declare-styleable>
</resources>
根据需求,我们定义了字体,字体颜色,字体大小,控件默认颜色,已经下载部分颜色,未下载部分颜色,控件的形状[矩形,圆角矩形],一共7个属性,format是值该属性的取值类型:

一共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag;不清楚的可以google一把。

然后在布局中声明我们的自定义View

<RelativeLayout xmlns:android=""xmlns:tools=""android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context="com.ruffian.android.MainActivity$PlaceholderFragment" ><com.ruffian.android.view.RuffianProgressBarLinexmlns:custom=""android:id="@+id/progressBarLine1"android:layout_width="100dp"android:layout_height="30dp"android:padding="10dp"custom:cornerRadius="20dp"custom:defaultColor="#9ACF51"custom:downloadedColor="#ec7883"custom:text="下载"custom:textColor="@android:color/white"custom:textSize="16sp"custom:undownloadColor="#cdcdcd" /><com.ruffian.android.view.RuffianProgressBarLinexmlns:custom=""android:id="@+id/progressBarLine2"android:layout_width="100dp"android:layout_height="30dp"android:layout_alignParentRight="true"android:padding="10dp"custom:cornerRadius="0dp"custom:defaultColor="#f29b76"custom:downloadedColor="#e55a7f"custom:text="下载"custom:textColor="@android:color/white"custom:textSize="16sp"custom:undownloadColor="#fb9090" /><TextViewandroid:id="@+id/progressText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/progressBarLine1"android:layout_centerHorizontal="true"android:padding="10dp"android:text="下载进度 " /><Buttonandroid:id="@+id/button"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="再玩一次" /></RelativeLayout>
布局中展示不同的控件形状,同时展示下载进度百分比

注意:一定要引入 xmlns:custom=""我们的命名空间,后面也可以是包路径:com.ruffian.android.view

2、在View的构造方法中,获得我们的自定义的样式

// 默认public static final String STATE_DEFAULT = "DEFAULT";// 安装public static final String STATE_INSTALL = "INSTALL";// 暂停public static final String STATE_STOP = "STOP";// 下载public static final String STATE_DOWNLOAD = "DOWNLOAD";// 打开public static final String STATE_OPEN = "OPEN";// 最大值100private static final float MAX_PROGRESS = 100;/*** 控件四周圆弧角度,0:矩形<br/>* 不设置或者设置为0的情况是矩形,其他情况是圆角矩形* */private float mCornerRadius;/*** 文字*/private String mText = "";/*** 字体颜色*/private int mTextColor;/*** 字体大小*/private int mTextSize;/*** 控件默认颜色*/private int mDefaultColor;/*** 默认颜色*/private final String DEF_DEFAULTCOLOR = "#9ACF51";/*** 未下载部分颜色*/private int mUnDownloadColor;/*** 默认颜色-下载进度条背景*/private final String DEF_BACKGROUDCOLOR = "#cdcdcd";/*** 已经下载部分颜色*/private int mDownloadedColor;/*** 默认颜色-下载进度*/private final String DEF_DOWNLOADCOLOR = "#ec7883";/*** 矩形,绘制文字需要用*/private Rect mRect;/*** 圆角矩形*/private RectF mRectF;/*** 画笔,属性值可能改变*/private Paint mPaint;/*** 文字画笔,初始化之后属性不再改变*/private Paint mTextPaint;/*** 控件状态*/private String mState = STATE_DEFAULT;/*** 下载进度{这里根据需求定基础类型,也可以是float[0.0f,1.0f]}*/private int mProgress;public RuffianProgressBarLine(Context context, AttributeSet attrs) {this(context, attrs, 0);}public RuffianProgressBarLine(Context context) {this(context, null);}public RuffianProgressBarLine(Context context, AttributeSet attrs,int defStyleAttr) {super(context, attrs, defStyleAttr);// 获取自定义的控件TypedArray typedArray = getContext().obtainStyledAttributes(attrs,R.styleable.RuffianProgressBarLine, defStyleAttr, 0);int parameterCount = typedArray.getIndexCount();for (int i = 0; i < parameterCount; i++) {int attr = typedArray.getIndex(i);switch (attr) {case R.styleable.RuffianProgressBarLine_cornerRadius:mCornerRadius = typedArray.getDimensionPixelSize(attr, 0);break;case R.styleable.RuffianProgressBarLine_text:mText = typedArray.getString(attr);break;case R.styleable.RuffianProgressBarLine_textColor:mTextColor = typedArray.getColor(attr, 0);break;case R.styleable.RuffianProgressBarLine_textSize:mTextSize = typedArray.getDimensionPixelSize(attr, 12);break;case R.styleable.RuffianProgressBarLine_defaultColor:mDefaultColor = typedArray.getColor(attr,Color.parseColor(DEF_DEFAULTCOLOR));break;case R.styleable.RuffianProgressBarLine_undownloadColor:mUnDownloadColor = typedArray.getColor(attr,Color.parseColor(DEF_BACKGROUDCOLOR));break;case R.styleable.RuffianProgressBarLine_downloadedColor:mDownloadedColor = typedArray.getColor(attr,Color.parseColor(DEF_DOWNLOADCOLOR));break;}}typedArray.recycle();mPaint = new Paint();mRect = new Rect();mRectF = new RectF();// 初始化之后不再改变,直接设置属性mTextPaint = new Paint();// 设置抗锯齿,圆滑处理mTextPaint.setAntiAlias(true);// 设置画笔类型mTextPaint.setStyle(Style.FILL);// 设置画笔颜色mTextPaint.setColor(mTextColor);// 设置字体大小mTextPaint.setTextSize(mTextSize);}
我们重写了3个构造方法,默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造调用我们的三个参数的构造,我们在三个参数的构造中获得自定义属性。

3、我们重写 onDraw,onMesure 调用系统提供的:

/*** 重写计算控件宽高函数*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 获取宽高的设置模式int withMode = MeasureSpec.getMode(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);// 获取宽高的大小int withSize = MeasureSpec.getSize(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);// 最终宽高int height = getSizeInMode(heightSize, heightMode, 1);int width = getSizeInMode(withSize, withMode, 0);// 最终设置宽高setMeasuredDimension(width, height);}/*** 获取不同mode下宽高的实际值<br/>* type[0:宽,1:高]* * @param size初始值* @param mode设置类型* @param type* @return* @author Ruffian* @date 2015年12月11日*/private int getSizeInMode(int size, int mode, int type) {// 返回值int sizeValue = 0;switch (mode) {case MeasureSpec.EXACTLY:// 设置了明确的值,直接使用sizeValue = size;break;case MeasureSpec.AT_MOST:// WARP_CONTENT时候,先计算绘制文本的大小mTextPaint.setTextSize(mTextSize);mTextPaint.getTextBounds(mText, 0, mText.length(), mRect);// 再计算[左右,上下]的padding值int desired = 0;if (type == 0) {// 文本宽度+左右paddingfloat textWidth = mRect.width();desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());} else if (type == 1) {// 文本宽度+上下paddingfloat textHeight = mRect.height();desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());}sizeValue = desired;break;case MeasureSpec.UNSPECIFIED:// 不处理break;}return sizeValue;}/*** 重写绘制函数onDraw*/@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 设置抗锯齿,圆滑处理mPaint.setAntiAlias(true);// 设置画笔类型mPaint.setStyle(Style.FILL);// 绘制控件canvasViewOnLogic(canvas);}/*** 根据业务逻辑绘制控件* * @param canvas* @author Ruffian* @date 2015年12月11日*/private void canvasViewOnLogic(Canvas canvas) {/*** 下载中和暂停状态是特殊情况,需要画两层视图,其他情况只需要一层*/if (mState.equals(STATE_DOWNLOAD) || mState.equals(STATE_STOP)) {// 暂停状态--下载中状态// 绘制时mProgress要转化成float类型,区间[0.0f,1.0f]drawDownloadView(canvas, mDownloadedColor, 0,(int) ((mProgress / MAX_PROGRESS) * getWidth()));drawDownloadView(canvas, mUnDownloadColor,(int) ((mProgress / MAX_PROGRESS) * getWidth()), getWidth());} else {// 其他状态// 设置默认画笔颜色mPaint.setColor(mDefaultColor);// 设置矩形,宽度是控件大小mRectF = new RectF(0, 0, getWidth(), getHeight());// 画底部矩形canvas.drawRoundRect(mRectF, mCornerRadius, mCornerRadius, mPaint);}// 计算文字mTextPaint.getTextBounds(mText, 0, mText.length(), mRect);// 绘制文字居中canvas.drawText(mText, getWidth() / 2 - mRect.width() / 2, getHeight()/ 2 + mRect.height() / 2, mTextPaint);}/*** 绘制下载状态的view<br/>* 理解:绘制两次相同的view,不同颜色区分,一个绘制前半部分,一部分绘制后半部分* * @param canvas* @param color* @param startX开始绘制的X* @param endX结束绘制的X* @author Ruffian* @date 2015年12月11日*/private void drawDownloadView(Canvas canvas, int color, int startX, int endX) {mPaint.setColor(color);// 设置矩形,宽度是控件大小mRectF = new RectF(0, 0, getWidth(), getHeight());canvas.save(Canvas.CLIP_SAVE_FLAG);canvas.clipRect(startX, 0, endX, getMeasuredHeight());canvas.drawRoundRect(mRectF, mCornerRadius, mCornerRadius, mPaint);canvas.restore();}

文章开头说 onMeasure 函数存在特殊情况。这种特殊情况是针对,布局文件中 layout_width , layout_height。

当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。

所以,当设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMesure方法”:

重写之前先了解MeasureSpec的specMode,一共三种类型:

EXACTLY:一般是设置了明确的值或者是MATCH_PARENT

AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT

UNSPECIFIED:表示子布局想要多大就多大,很少使用


/*** 重写计算控件宽高函数*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 获取宽高的设置模式int withMode = MeasureSpec.getMode(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);// 获取宽高的大小int withSize = MeasureSpec.getSize(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);// 最终宽高int height = getSizeInMode(heightSize, heightMode, 1);int width = getSizeInMode(withSize, withMode, 0);// 最终设置宽高setMeasuredDimension(width, height);}/*** 获取不同mode下宽高的实际值<br/>* type[0:宽,1:高]* * @param size初始值* @param mode设置类型* @param type* @return* @author Ruffian* @date 2015年12月11日*/private int getSizeInMode(int size, int mode, int type) {// 返回值int sizeValue = 0;switch (mode) {case MeasureSpec.EXACTLY:// 设置了明确的值,直接使用sizeValue = size;break;case MeasureSpec.AT_MOST:// WARP_CONTENT时候,先计算绘制文本的大小mTextPaint.setTextSize(mTextSize);mTextPaint.getTextBounds(mText, 0, mText.length(), mRect);// 再计算[左右,上下]的padding值int desired = 0;if (type == 0) {// 文本宽度+左右paddingfloat textWidth = mRect.width();desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());} else if (type == 1) {// 文本宽度+上下paddingfloat textHeight = mRect.height();desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());}sizeValue = desired;break;case MeasureSpec.UNSPECIFIED:// 不处理break;}return sizeValue;}


这里特别说明一下 onDraw方法

如果是在矩形的情况下是很简答的一种实现:

先画一个底部的矩形(表示未下载),然后再重新设置画笔颜色再画一个(表示进度)矩形。看起来就能达到下载进度的效果

但是当我们设置属性为 圆角矩形(cornerRadius>0)的时候,我发现效果不是我想要的

运行结果是:这样的,这样的



但是我们想要的是:这样的,这样的



由于刚开始自定义控件,很多属性和用法都不知道怎么用,折腾了好久,后来在网上看到说  canvas 有个 clipRect 的方法,good ,那么修改一下绘制部分的代码就可以了

起初代码

	// 暂停状态--下载中状态// 设置底部矩形颜色mPaint.setColor(mBackgroudColor);// 设置矩形,宽度是控件大小mRectF = new RectF(0, 0, getWidth(), getHeight());// 画底部矩形canvas.drawRoundRect(mRectF, mCornerRadius, mCornerRadius, mPaint);// 设置进度矩形颜色mPaint.setColor(mDownloadColor);// 设置矩形,宽度是实际进度mRectF = new RectF(0, 0, (mProgress / MAX_PROGRESS) * getWidth(),getHeight());// 画进度矩形canvas.drawRoundRect(mRectF, mCornerRadius, mCornerRadius, mPaint);


 修改关键方法 
 
  

/*** 绘制下载状态的view<br/>* 理解:绘制两次相同的view,不同颜色区分,一个绘制前半部分,一部分绘制后半部分* * @param canvas* @param color* @param startX开始绘制的X* @param endX结束绘制的X* @author Ruffian* @date 2015年12月11日*/private void drawDownloadView(Canvas canvas, int color, int startX, int endX) {mPaint.setColor(color);// 设置矩形,宽度是控件大小mRectF = new RectF(0, 0, getWidth(), getHeight());canvas.save(Canvas.CLIP_SAVE_FLAG);canvas.clipRect(startX, 0, endX, getMeasuredHeight());canvas.drawRoundRect(mRectF, mCornerRadius, mCornerRadius, mPaint);canvas.restore();}
// 暂停状态--下载中状态// 绘制时mProgress要转化成float类型,区间[0.0f,1.0f]drawDownloadView(canvas, mDownloadedColor, 0,(int) ((mProgress / MAX_PROGRESS) * getWidth()));drawDownloadView(canvas, mUnDownloadColor,(int) ((mProgress / MAX_PROGRESS) * getWidth()), getWidth());

恩恩,这下好了,终于是我们想要的效果了,接下来看看activity模拟下载整个流程,使用自定义的下载控件

activity代码

package com.ruffian.android;import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;import com.ruffian.android.view.RuffianProgressBarLine;@SuppressLint("HandlerLeak")
public class MainActivity extends Activity {private RuffianProgressBarLine mProgressBarLine;private Button mButton;private TextView mProgressText;// 下载进度int mProgress = 0;boolean isLoading = false;private String viewState;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mProgressBarLine = (RuffianProgressBarLine) findViewById(R.id.progressBarLine1);mProgressText = (TextView) findViewById(R.id.progressText);mButton = (Button) findViewById(R.id.button);mProgressBarLine.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View arg0) {viewState = mProgressBarLine.getState();if (viewState.equals(RuffianProgressBarLine.STATE_DEFAULT)) {// 下载中mProgressBarLine.setState(RuffianProgressBarLine.STATE_DOWNLOAD);mProgressBarLine.setText("暂停");isLoading = true;download();} else if (viewState.equals(RuffianProgressBarLine.STATE_DOWNLOAD)) {// 暂停mProgressBarLine.setState(RuffianProgressBarLine.STATE_STOP);mProgressBarLine.setText("继续");isLoading = false;// download();} else if (viewState.equals(RuffianProgressBarLine.STATE_STOP)) {// 继续mProgressBarLine.setState(RuffianProgressBarLine.STATE_DOWNLOAD);mProgressBarLine.setText("暂停");isLoading = true;// download();} else if (viewState.equals(RuffianProgressBarLine.STATE_INSTALL)) {// 安装mProgressBarLine.setState(RuffianProgressBarLine.STATE_INSTALL);mProgressBarLine.setText("安装");} else if (viewState.equals(RuffianProgressBarLine.STATE_OPEN)) {// 运行mProgressBarLine.setState(RuffianProgressBarLine.STATE_DOWNLOAD);mProgressBarLine.setText("运行");}}});mButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View arg0) {mProgress = 0;isLoading = false;mProgressBarLine.setState(RuffianProgressBarLine.STATE_DEFAULT);mProgressBarLine.setText("下载");mProgressText.setText("下载进度 ");}});}/*** 下载,暂停* * @author Ruffian* @date 2015年12月11日*/public void download() {new Thread() {public void run() {while (mProgress <= 100) {if (mProgress == 100) {// 进度满100,状态改为安装mProgressBarLine.setState(RuffianProgressBarLine.STATE_INSTALL);mProgressBarLine.setText("安装");}// 是否正在下载if (isLoading) {// 更新UIuiHandler.sendMessage(uiHandler.obtainMessage(1001,mProgress));mProgressBarLine.setProgress(mProgress);mProgress++;}// Log.w("sss", "" + mProgress);try {Thread.sleep(80);// 进度改变速度} catch (InterruptedException e) {e.printStackTrace();}}};}.start();}/*** 更新UI*/Handler uiHandler = new Handler() {public void handleMessage(android.os.Message msg) {switch (msg.what) {case 1001:int progress = (int) msg.obj;if (progress == 100) {mProgressText.setText("下载完成");} else {mProgressText.setText(String.valueOf(progress) + "%");}break;}};};}

小弟第一次尝试自定义控件的实现,前辈们要是看见哪里有不正确的地方,还恳请耐心指正,同时小伙伴们在学习过程中遇到什么问题可以留言讨论, 紧急情况发送邮件:632835821@qq 

源码下载



更多推荐

Android自定义view之下载控件,ProgressBar

本文发布于:2024-03-04 16:17:21,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1709767.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:自定义   控件   Android   ProgressBar   view

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!