前言:這是自己寫的練手Demo,以后會寫一個進階版的水漫效果按鈕,長按出現波浪形狀的水波,更好看,可玩性更高,感興趣的可以關注下后續,
目錄
- 效果展示
- 實作方法
- 一、思路
- 二、代碼實作
- 三、完整代碼及使用示例
- Demo示例
- 結語
效果展示

實作方法
一、思路
通過自定義View畫出一個長按出現水漫效果進度條的按鈕,當進度條滿了進行介面回呼,告訴當前運行的Activity,影片執行完畢,
1.畫出中心帶圓角的長方形按鈕,
- 確定自定義View的大小尺寸,
- 畫出中心圖片,
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進行參考
<?xml version="1.0" encoding="utf-8"?>
<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 -->
<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();
}
});
}
}
Demo示例

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
共勉!
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/258764.html
標籤:其他
