前言:这是自己写的练手Demo,以后会写一个进阶版的水漫效果按钮,长按出现波浪形状的水波,更好看,可玩性更高,感兴趣的可以关注下后续。
通过自定义View画出一个长按出现水漫效果进度条的按钮,当进度条满了进行接口回调,告诉当前运行的Activity,动画执行完毕。
1.画出中心带圆角的长方形按钮。
2.确定水漫进度条的大小,先将进度条的形状、颜色、大小提前设置好。
3.监听点击事件,利用事件分发机制,当手指点击按钮时,将提前设置好的水漫进度条以一部分的形式显示出来,覆盖在原有的背景上,用逐渐增加和递减的数值控制水漫进度条占总体的占比。
自定义View核心代码:
1.属性的初始化和接口
private void getAttrValue(AttributeSet attrs) {
TypedArray ta = mContext.obtainStyledAttributes(attrs, R.styleable.WaterProgressView);
mBgColor = ta.getColor(R.styleable.WaterProgressView_background_color, Color.parseColor("#EE191C"));
mProgressColor = ta.getColor(R.styleable.WaterProgressView_progress_color, Color.parseColor("#FF5263"));
ta.recycle();
}
private void init() {
//初始化画带圆角矩形的画笔
mBgPaint = new Paint();
mBgPaint.setColor(mBgColor);
mBgPaint.setStyle(Paint.Style.FILL);
mBgPaint.setAntiAlias(true);
mBgPaint.setDither(true);
//初始化画进度的paint
mProgressPaint = new Paint();
mProgressPaint.setColor(mProgressColor);
mProgressPaint.setAntiAlias(true);
mProgressPaint.setDither(true);
mProgressPaint.setStyle(Paint.Style.FILL);
//初始化画完整进度的paint
mProgressBgPaint = new Paint();
mProgressBgPaint.setColor(mProgressColor);
mProgressBgPaint.setAntiAlias(true);
mProgressBgPaint.setDither(true);
mProgressBgPaint.setStyle(Paint.Style.FILL);
//显示背景的矩形范围
mRectangleRectF = new RectF();
//显示进度的矩形范围
mProgressRectangleRectF = new RectF();
mHandler = new Handler();
//环形进度条自动增加逻辑
mRunnable = new Runnable() {
@Override
public void run() {
mProgress += 1;
setProgress(mProgress);
//更新进度的接口回调
if (mOnLongClickStateListener != null){
mOnLongClickStateListener.onProgress(mProgress);
}
if (mProgress < mTargetProgress){
mHandler.postDelayed(this, 1);
}else {
//当环形进度条达到100,取消循环,进度置零,调用接口的完成回调
mProgress = 0;
if (mOnLongClickStateListener != null){
mOnLongClickStateListener.onFinish();
}
}
}
};
//取消动作的逻辑
mCancelRunnable = new Runnable() {
@Override
public void run() {
setProgress(mProgress);
//当进度为0时,取消循环
if (mProgress <= 0){
return;
}else if (mProgress < 10){
//当进度降低到较低状态时,减缓降低的速度,每次减2
mProgress -= 2;
}else {
//进度较高时,进度条减少的速度加快,每次减7
mProgress -= 7;
}
if (mProgress > 0){
mHandler.postDelayed(this, FINISH_TIME / 100);
}else {
//当环形进度条达到0,再次手动置零,调用接口的取消回调,并返回进度回调参数
mProgress = 0;
if (mOnLongClickStateListener != null){
mOnLongClickStateListener.onCancel();
}
}
//更新进度的接口回调
if (mOnLongClickStateListener != null){
mOnLongClickStateListener.onProgress(mProgress);
}
}
};
}
/**
* 长按完成和取消的接口
*/
public interface OnLongClickStateListener {
void onFinish();
void onProgress(float progress);
void onCancel();
}
2.重写onTouchEvent(),利用事件分发,刷新按钮的状态
@Override
public boolean onTouchEvent(MotionEvent motionEvent) {
// Log.e("TAG", "onTouchEvent: " + mProgress );
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
//当手指按下时,执行环形进度条增加Runnable,进度条开始增加
mHandler.post(mRunnable);
mHandler.removeCallbacks(mCancelRunnable);
Log.e("TAG", "onTouchEvent: ACTION_DOWN");
return true;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
//当手指松开时,执行环形进度条减少Runnable,进度条开始减少
mHandler.removeCallbacks(mRunnable);
mHandler.post(mCancelRunnable);
Log.e("TAG", "onTouchEvent: ACTION_UP");
break;
case MotionEvent.ACTION_MOVE:
break;
}
return false;
}
3.重写onDraw方法和onSizeChange方法
每次调用invalidate()方法都会进行一次onDraw。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画背景圆角矩形
canvas.drawRoundRect(mRectangleRectF, mViewWidth / 2, mViewWidth / 2, mBgPaint);
//进度圆角矩形,只画出完整进度的(mProgress / mTargetProgress)部分,主需要控制改画出的部分的四个顶点坐标,即可单独画出顶点内的部分
canvas.drawRect(0, mViewHeight * (1 - mProgress / mTargetProgress), mViewWidth, mViewHeight, mProgressPaint);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
//背景的矩形
mRectangleRectF.set(0, 0, mViewWidth, mViewHeight);
//进度的矩形,因为防止计算数值时精度丢失,导致进度条面积小于实际背景面积,当水漫进度增加时,无法完全覆盖背景,所以范围增加1px
mProgressRectangleRectF.set(-1, - 1, mViewWidth + 1, mViewHeight + 1);
dealBitmap();
}
4.处理bitmap方法
将完整的水漫进度条画到bitmap里,通过mProgressPaint.setShader()方法,将完整的bitmap设置到画笔里,那么这个歌画笔就可以带有完整的水漫进度,我们只要使用这个画笔,只显示我们需要画的那一部分就好。
//处理Bitmap
private void dealBitmap(){
mBitmap = Bitmap.createBitmap(mViewWidth,
mViewHeight , Bitmap.Config.ARGB_8888);
//把完整进度画进bitmap
Canvas canvas = new Canvas(mBitmap);
canvas.drawRoundRect(mProgressRectangleRectF, mViewWidth / 2, mViewWidth / 2, mProgressBgPaint);
mProgressPaint.setShader(new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
}
1.自定义View完整代码
public class WaterProgressView extends View {
private Context mContext;
private Paint mBgPaint; // 背景的画笔
private Paint mProgressBgPaint; // 完整进度背景的画笔
private Paint mProgressPaint; // 进度的画笔
private RectF mRectangleRectF; //背景范围矩形
private RectF mProgressRectangleRectF; //进度(水漫)范围矩形
private int mViewWidth;//当前View的宽度
private int mViewHeight;//当前View的高度
private int mBgColor; //背景颜色
private float mProgress; //进度
private int mTargetProgress = 100; //最大进度
private int mProgressColor; //进度颜色
private Bitmap mBitmap; //完整进度的bitmap对象
private Handler mHandler;
private Runnable mRunnable; //长按动作的计时器
private Runnable mCancelRunnable; //取消动作的计时器
private static final int FINISH_TIME = 1000;
private WaterProgressView.OnLongClickStateListener mOnLongClickStateListener;
public void setOnLongClickStateListener(WaterProgressView.OnLongClickStateListener onLongClickStateListener) {
this.mOnLongClickStateListener = onLongClickStateListener;
}
public WaterProgressView(Context context) {
this(context,null);
}
public WaterProgressView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public WaterProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
getAttrValue(attrs);
init();
}
private void getAttrValue(AttributeSet attrs) {
TypedArray ta = mContext.obtainStyledAttributes(attrs, R.styleable.WaterProgressView);
mBgColor = ta.getColor(R.styleable.WaterProgressView_background_color, Color.parseColor("#EE191C"));
mProgressColor = ta.getColor(R.styleable.WaterProgressView_progress_color, Color.parseColor("#FF5263"));
ta.recycle();
}
private void init() {
//初始化画带圆角矩形的画笔
mBgPaint = new Paint();
mBgPaint.setColor(mBgColor);
mBgPaint.setStyle(Paint.Style.FILL);
mBgPaint.setAntiAlias(true);
mBgPaint.setDither(true);
//初始化画进度的paint
mProgressPaint = new Paint();
mProgressPaint.setColor(mProgressColor);
mProgressPaint.setAntiAlias(true);
mProgressPaint.setDither(true);
mProgressPaint.setStyle(Paint.Style.FILL);
//初始化画完整进度的paint
mProgressBgPaint = new Paint();
mProgressBgPaint.setColor(mProgressColor);
mProgressBgPaint.setAntiAlias(true);
mProgressBgPaint.setDither(true);
mProgressBgPaint.setStyle(Paint.Style.FILL);
//显示背景的矩形范围
mRectangleRectF = new RectF();
//显示进度的矩形范围
mProgressRectangleRectF = new RectF();
mHandler = new Handler();
//环形进度条自动增加逻辑
mRunnable = new Runnable() {
@Override
public void run() {
mProgress += 1;
setProgress(mProgress);
//更新进度的接口回调
if (mOnLongClickStateListener != null){
mOnLongClickStateListener.onProgress(mProgress);
}
if (mProgress < mTargetProgress){
mHandler.postDelayed(this, 1);
}else {
//当环形进度条达到100,取消循环,进度置零,调用接口的完成回调
mProgress = 0;
if (mOnLongClickStateListener != null){
mOnLongClickStateListener.onFinish();
}
}
}
};
//取消动作的逻辑
mCancelRunnable = new Runnable() {
@Override
public void run() {
setProgress(mProgress);
//当进度为0时,取消循环
if (mProgress <= 0){
return;
}else if (mProgress < 10){
//当进度降低到较低状态时,减缓降低的速度,每次减2
mProgress -= 2;
}else {
//进度较高时,进度条减少的速度加快,每次减7
mProgress -= 7;
}
if (mProgress > 0){
mHandler.postDelayed(this, FINISH_TIME / 100);
}else {
//当环形进度条达到0,再次手动置零,调用接口的取消回调,并返回进度回调参数
mProgress = 0;
if (mOnLongClickStateListener != null){
mOnLongClickStateListener.onCancel();
}
}
//更新进度的接口回调
if (mOnLongClickStateListener != null){
mOnLongClickStateListener.onProgress(mProgress);
}
}
};
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画背景圆角矩形
canvas.drawRoundRect(mRectangleRectF, mViewWidth / 2, mViewWidth / 2, mBgPaint);
//进度圆角矩形,只画出完整进度的(mProgress / mTargetProgress)部分,主需要控制改画出的部分的四个顶点坐标,即可单独画出顶点内的部分
canvas.drawRect(0, mViewHeight * (1 - mProgress / mTargetProgress), mViewWidth, mViewHeight, mProgressPaint);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
//背景的矩形
mRectangleRectF.set(0, 0, mViewWidth, mViewHeight);
//进度的矩形,因为防止计算数值时精度丢失,导致进度条面积小于实际背景面积,当水漫进度增加时,无法完全覆盖背景,所以范围增加1px
mProgressRectangleRectF.set(-1, - 1, mViewWidth + 1, mViewHeight + 1);
dealBitmap();
}
@Override
public boolean onTouchEvent(MotionEvent motionEvent) {
// Log.e("TAG", "onTouchEvent: " + mProgress );
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
//当手指按下时,执行环形进度条增加Runnable,进度条开始增加
mHandler.post(mRunnable);
mHandler.removeCallbacks(mCancelRunnable);
Log.e("TAG", "onTouchEvent: ACTION_DOWN");
return true;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
//当手指松开时,执行环形进度条减少Runnable,进度条开始减少
mHandler.removeCallbacks(mRunnable);
mHandler.post(mCancelRunnable);
Log.e("TAG", "onTouchEvent: ACTION_UP");
break;
case MotionEvent.ACTION_MOVE:
break;
}
return false;
}
//处理Bitmap
private void dealBitmap(){
mBitmap = Bitmap.createBitmap(mViewWidth,
mViewHeight , Bitmap.Config.ARGB_8888);
//把完整进度画进bitmap
Canvas canvas = new Canvas(mBitmap);
canvas.drawRoundRect(mProgressRectangleRectF, mViewWidth / 2, mViewWidth / 2, mProgressBgPaint);
mProgressPaint.setShader(new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
}
/**
* 设置背景颜色
* @param
*/
public void setBgColor(int bgColor) {
this.mBgColor = bgColor;
mBgPaint.setColor(mBgColor);
invalidate();
}
/**
* 设置进度颜色
* @param progressColor 进度颜色
*/
public void setProgressColor(int progressColor) {
this.mProgressColor = progressColor;
//设置进度未满时的画笔颜色
mProgressPaint.setColor(mProgressColor);
//设置进度满时完整的画笔颜色
mProgressBgPaint.setColor(mProgressColor);
invalidate();
}
/**
* 设置进度
* @param mProgress(1-100f)
*/
public void setProgress(float mProgress) {
if(mProgress < 0){
mProgress = 0;
} else if(mProgress > 100){
mProgress = 100;
}
this.mProgress = mProgress;
invalidate();
}
/**
* 长按完成和取消的接口
*/
public interface OnLongClickStateListener {
void onFinish();
void onProgress(float progress);
void onCancel();
}
}
2.在style文件中,定义attr属性,没有则新建一个
attr.xml的内容:
<declare-styleable name="LongClickProgressView">
<attr name="centerDrawable" format="reference"/>
declare-styleable>
3.在xml进行引用
<androidx.constraintlayout.widget.ConstraintLayout
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"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:text="0%"
android:textColor="#000000"
android:textSize="39dp"
android:layout_marginTop="60dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.longclickprogresswaterview.WaterProgressView
android:id="@+id/btn_long_click_finish"
android:layout_width="96dp"
android:layout_height="48dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@id/btn_long_click_finish"
app:layout_constraintEnd_toEndOf="@id/btn_long_click_finish"
app:layout_constraintTop_toTopOf="@id/btn_long_click_finish"
app:layout_constraintBottom_toBottomOf="@id/btn_long_click_finish"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:text="Finish"
android:textAllCaps="true"
android:textSize="12sp"
android:textColor="#ffffff" />
androidx.constraintlayout.widget.ConstraintLayout>
4.在Activity中对自定义View进行修改
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView textView = findViewById(R.id.tv_progress);
WaterProgressView longClickProgressView = findViewById(R.id.btn_long_click_finish);
longClickProgressView.setBgColor(Color.parseColor("#000000")); //设置背景颜色
longClickProgressView.setProgressColor(Color.parseColor("#FFFFFF")); //设置进度(水漫)颜色
//设置进度监听回调
longClickProgressView.setOnLongClickStateListener(new WaterProgressView.OnLongClickStateListener() {
@Override
public void onFinish() {
//当完成读条时执行
Toast.makeText(MainActivity.this, "Finish!", Toast.LENGTH_SHORT).show();
}
@Override
public void onProgress(float progress) {
//进度条改变时执行
textView.setText(progress + "%");
}
@Override
public void onCancel() {
//取消长按时执行
Toast.makeText(MainActivity.this, "Cancel!", Toast.LENGTH_SHORT).show();
}
});
}
}
2.Demo地址
Github链接,欢迎Star和给出宝贵意见
https://github.com/Dengyaohui/LongClickProgressWaterViewDemo
CSDN下载地址
https://download.csdn.net/download/Nobody_else_/15184398
后面会写一个进阶版的水漫效果按钮,长按出现波浪形状的水波,更加好看,可玩性更高。
更多其他的自定义View,可以看我的其他博客,欢迎批评指正。
仿Keep长按出现进度条的按钮:
https://blog.csdn.net/Nobody_else_/article/details/113186425
共勉!