English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
L'effetto di implementazione è come segue:
Pensiero di implementazione:
1, come ottenere l'effetto di aumento dell'acqua nella circonferenza: utilizzando l'attributo setXfermode di Paint con PorterDuff.Mode.SRC_IN per disegnare l'intersezione tra il rettangolo del progresso e la circonferenza
2, come ottenere l'effetto delle onde d'acqua: utilizzando la curva di Bézier, cambiare dinamicamente il picco delle onde per realizzare l'effetto "con l'aumentare del progresso, le onde d'acqua si riducono gradualmente"
Non facciamo troppe chiacchiere, guardiamo il codice.
Prima di tutto, i valori delle proprietà personalizzate, quali valori personalizzabili ci sono?
Il colore di sfondo della circonferenza: circle_color, il colore del progresso: progress_color, il colore del testo del progresso: text_color, la dimensione del testo del progresso: text_size, e l'ultimo: altezza massima delle onde: ripple_topheight
<declare-styleable name="WaterProgressView"> <attr name="circle_color" format="color"/><!--圆的颜色--> <attr name="progress_color" format="color"/><!--进度的颜色--> <attr name="text_color" format="color"/><!--文字的颜色--> <attr name="text_size" format="dimension"/><!--文字大小--> <attr name="ripple_topheight" format="dimension"/><!--水波涟漪最大高度--> </declare-styleable>
下面是自定义View:WaterProgressView的部分代码:
成员变量
public class WaterProgressView extends ProgressBar { //默认圆的背景色 public static final int DEFAULT_CIRCLE_COLOR = 0xff00cccc; //默认进度的颜色 public static final int DEFAULT_PROGRESS_COLOR = 0xff00CC66; //默认文字的颜色 public static final int DEFAULT_TEXT_COLOR = 0xffffffff; //默认文字的大小 public static final int DEFAULT_TEXT_SIZE = 18; //默认的波峰最高点 public static final int DEFAULT_RIPPLE_TOPHEIGHT = 10; private Context mContext; private Canvas mPaintCanvas; private Bitmap mBitmap; //画圆的画笔 private Paint mCirclePaint; //画圆的画笔的颜色 private int mCircleColor; //画进度的画笔 private Paint mProgressPaint; //画进度的画笔的颜色 private int mProgressColor ; //画进度的path private Path mProgressPath; //贝塞尔曲线波峰最大值 private int mRippleTop = 10; //进度文字的画笔 private Paint mTextPaint; //colore del testo di progresso private int mTextColor; private int mTextSize = 18; //progresso target, ovvero il progresso delle attività gestite con il doppio clic, influisce sull'amplitude della curva private int mTargetProgress = 50; //ascolta gli eventi di doppio clic e singolo clic private GestureDetector mGestureDetector; }
ottieni il valore dell'attributo personalizzato:
private void getAttrValue(AttributeSet attrs) { TypedArray ta = mContext.obtainStyledAttributes(attrs, R.styleable.WaterProgressView); mCircleColor = ta.getColor(R.styleable.WaterProgressView_circle_color, DEFAULT_CIRCLE_COLOR); mProgressColor = ta.getColor(R.styleable.WaterProgressView_progress_color, DEFAULT_PROGRESS_COLOR); mTextColor = ta.getColor(R.styleable.WaterProgressView_text_color, DEFAULT_TEXT_COLOR); mTextSize = (int) ta.getDimension(R.styleable.WaterProgressView_text_size, DesityUtils.sp2px(mContext, DEFAULT_TEXT_SIZE)); mRippleTop = (int) ta.getDimension(R.styleable.WaterProgressView_ripple_topheight, DesityUtils.dp2px(mContext, DEFAULT_RIPPLE_TOPHEIGHT)); ta.recycle(); }
definisci il costruttore, presta attenzione a mProgressPaint.setXfermode
//当new该类时调用此构造函数 public WaterProgressView(Context context) { this(context,null); } //当xml文件中定义该自定义View时调用此构造函数 public WaterProgressView(Context context, AttributeSet attrs) { this(context, attrs,0); } public WaterProgressView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; getAttrValue(attrs); //初始化画笔的相关属性 initPaint(); mProgressPath = new Path(); } private void initPaint() { //初始化画圆的paint mCirclePaint = new Paint(); mCirclePaint.setColor(mCircleColor); mCirclePaint.setStyle(Paint.Style.FILL); mCirclePaint.setAntiAlias(true); mCirclePaint.setDither(true); //初始化画进度的paint mProgressPaint = new Paint(); mProgressPaint.setColor(mProgressColor); mProgressPaint.setAntiAlias(true); mProgressPaint.setDither(true); mProgressPaint.setStyle(Paint.Style.FILL); //其实mProgressPaint画的也是矩形,当设置xfermode为PorterDuff.Mode.SRC_IN后则显示的为圆与进度矩形的交集,则为半圆 mProgressPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); //初始化画进度文字的画笔 mTextPaint = new Paint(); mTextPaint.setColor(mTextColor); mTextPaint.setStyle(Paint.Style.FILL); mTextPaint.setAntiAlias(true); mTextPaint.setDither(true); mTextPaint.setTextSize(mTextSize); }
Codice del metodo onMeasure():
@Override protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //Quando si utilizza, è necessario definire chiaramente le dimensioni di questo View, ovvero utilizzare il modello di misurazione MeasureSpec.EXACTLY int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(width,height); //Inizializziamo Bitmap, in modo che tutti i drawCircle, drawPath, drawText siano disegnati sul canvas del bitmap, quindi tracciamo questo bitmap sul canvas del metodo onDraw //Quindi il width e height di questo bitmap devono essere ridotti di padding left, top, right e bottom mBitmap = Bitmap.createBitmap(width-getPaddingLeft()-getPaddingRight(),height- getPaddingTop()-getPaddingBottom(), Bitmap.Config.ARGB_8888); mPaintCanvas = new Canvas(mBitmap); }
Il seguente è la parte centrale, il codice in onDraw. Prima di tutto, disegnamo Circle, la barra di progresso e il testo di progresso sul bitmap del canvas personalizzato, quindi tracciamo questo bitmap sul canvas nel metodo onDraw.
Il flusso di lavoro di drawPath è come segue:
Il codice di ratio è come segue, ovvero ratio è la percentuale di progresso corrente rispetto al progresso massimo
float ratio = getProgress() * 1.0f / getMax();
Poiché le coordinate si estendono in senso positivo verso il basso e destra dal punto B, le coordinate del punto A sono (width, (1 - ratio) * height), dove width è la larghezza del bitmap, height è l'altezza del bitmap. Prima di tutto, moviamo mProgressPath.moveTo al punto A, quindi determiniamo i vari punti chiave del path in senso orario dal punto A, come nella figura, quindi il codice è come segue:
int rightTop = (int) ((1-ratio)*height); mProgressPath.moveTo(width,rightTop); mProgressPath.lineTo(width,height); mProgressPath.lineTo(0,height); mProgressPath.lineTo(0,rightTop);
Così mProgressPath è arrivato a punto C, è necessario creare un effetto ondulatorio tra il punto A e il punto C, quindi disegnare una curva Bezier tra il punto A e il punto C.
Stabiliamo che il punto più alto del picco delle onde sia 10, quindi la lunghezza di una onda è di 40, è necessario disegnare width * 1.0f / 40 segmenti di questo tipo di curva, quindi il codice per disegnare la curva è come segue:
int count = (int) Math.ceil(width * 1.0f / (10 * 4)); for(int i=0; i<count; i++) { mProgressPath.rQuadTo(10, 10, 2 * 10, 0); mProgressPath.rQuadTo(10, -10, 2 * 10, 0); } mProgressPath.close(); mPaintCanvas.drawPath(mProgressPath,mProgressPaint);
In questo modo si può disegnare una barra di progresso con l'acqua che sale e con l'effetto delle onde. Ma dobbiamo anche realizzare che man mano che l'acqua sale, più si avvicina al progresso obiettivo, le onde dell'acqua dovrebbero diventare sempre più piccole. Quindi è necessario estrarre 10 come variabile e definirla come mRippleTop al valore massimo iniziale, quindi definire top come la curva di picco in tempo reale che si avvicina al progresso obiettivo man mano che il progresso si avvicina al progresso obiettivo, dove mTargetProgress è il progresso obiettivo, perché solo con un progresso obiettivo si può realizzare che il progresso corrente si avvicina continuamente al progresso obiettivo, l'acqua diventa sempre più piatta:
float top = (mTargetProgress-getProgress())*1.0f/mTargetProgress* mRippleTop;
Quindi l'aggiornamento del codice di drawPath è come segue:
float top = (mTargetProgress-getProgress())*1.0f/mTargetProgress* mRippleTop; for(int i=0; i<count; i++) { mProgressPath.rQuadTo(mRippleTop,top,2* mRippleTop,0); mProgressPath.rQuadTo(mRippleTop,-top,2* mRippleTop,0); }
Così si può davvero realizzare una barra di progresso con l'acqua che sale.
Ma come si realizza l'effetto della superficie dell'acqua che sale dal 0% al progresso obiettivo quando si fa doppio clic e che si muove continuamente al progresso obiettivo quando si fa clic singolo?
Parliamo dell'esecuzione dell'effetto di doppio clic: è semplice, definire un Handler, quando si fa doppio clic, handler.postDelayed(runnable, time), ogni un po' di tempo progress+1, nel runnable invalidate() aggiornare continuamente il progresso, fino a quando il progresso corrente raggiunge mTargetProgress.
Il codice è come segue
/** /* Implementazione dell'animazione di doppio tap */ */ private void startDoubleTapAnimation() { setProgress(0); doubleTapHandler.postDelayed(doubleTapRunnable, 60); } private Handler doubleTapHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; //Gestione thread di doppio tap, invia dati ogni 60ms private Runnable doubleTapRunnable = new Runnable() { @Override public void run() { if (getProgress() < mTargetProgress) { invalidate(); setProgress(getProgress() + 1); doubleTapHandler.postDelayed(doubleTapRunnable, 60); } doubleTapHandler.removeCallbacks(doubleTapRunnable); } } };
L'effetto di doppio tap è stato implementato, ma come si implementa l'effetto di singolo tap? Quando si fa un singolo tap, l'acqua deve continuare a muoversi per un periodo di tempo, le onde dell'acqua devono crescere gradualmente e poi l'acqua deve diventare piatta. Possiamo definire una variabile mSingleTapAnimationCount come il numero di volte che l'acqua si muove, e come nel trattamento del doppio tap, definire un Handler per inviare messaggi di aggiornamento dell'interfaccia ogni tanto, mSingleTapAnimationCount-- , poi facciamo alternare la cresta iniziale una volta positiva e una volta negativa, così possiamo ottenere l'effetto dell'acqua che si muove.
Codice principale come segue:
private void startSingleTapAnimation() { isSingleTapAnimation = true; singleTapHandler.postDelayed(singleTapRunnable,200); } private Handler singleTapHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; //Gestione thread di singolo tap, invia dati ogni 200ms private Runnable singleTapRunnable = new Runnable() { @Override public void run() { if(mSingleTapAnimationCount > 0) { invalidate(); mSingleTapAnimationCount--; singleTapHandler.postDelayed(singleTapRunnable,200); } singleTapHandler.removeCallbacks(singleTapRunnable); //是否正在进行单击动画 isSingleTapAnimation = false; //重置单击动画运行次数为50次 mSingleTapAnimationCount = 50; } } };
在onDraw中的代码进行相应的更改,因为单击和双击时drawPath中曲线部分的绘制逻辑不同,所以我们定义一个变量isSingleTapAnimation来区分是否正在进行单击动画或双击动画。
修改后的代码如下:
//绘制进度 mProgressPath.reset(); //从右上角开始绘制路径 int rightTop = (int) ((1-ratio)*height); mProgressPath.moveTo(width,rightTop); mProgressPath.lineTo(width,height); mProgressPath.lineTo(0,height); mProgressPath.lineTo(0,rightTop); //绘制贝塞尔曲线,形成波浪线 int count = (int) Math.ceil(width*1.0f/(mRippleTop *4)); //非单击动画状态 if(!isSingleTapAnimation&&getProgress()>0) { float top = (mTargetProgress-getProgress())*1.0f/mTargetProgress* mRippleTop; for(int i=0; i<count; i++) { mProgressPath.rQuadTo(mRippleTop,-top,2* mRippleTop,0); mProgressPath.rQuadTo(mRippleTop,top,2* mRippleTop,0); } } //单击animation状态,为了将效果放大,将mRippleTop放大2倍 //同时偶数时曲线走向如图所示,奇数时则曲线刚好相反 float top = (mSingleTapAnimationCount*1.0f/50)*10; //奇偶数时曲线切换 else { for(int i=0; i<count; i++) { mProgressPath.rQuadTo(mRippleTop *2,top*2,2* mRippleTop,0); mProgressPath.rQuadTo(mRippleTop *2,-top*2,2* mRippleTop,0); } } for(int i=0; i<count; i++) { mProgressPath.rQuadTo(mRippleTop *2,-top*2,2* mRippleTop,0); mProgressPath.rQuadTo(mRippleTop *2,top*2,2* mRippleTop,0); } } } mProgressPath.close(); mPaintCanvas.drawPath(mProgressPath,mProgressPaint);
基本上重要的代码与核心逻辑与代码就在上面了。
注意点:
1、当drawCircle时要考虑到padding,则circle的宽和高为getWidth与getHeight减去padding值,代码如下:
//自定义bitmap的宽和高 int width = getWidth()-getPaddingLeft()-getPaddingRight(); int height = getHeight()-getPaddingTop()-getPaddingBottom(); //画圆 mPaintCanvas.drawCircle(width/2,height/2,height/2,mCirclePaint);
2、当drawText时,不是从text的height的中间开始draw的,而是从baseline开始draw的
那如何获取baseline的height坐标呢
Paint.FontMetrics metrics = mTextPaint.getFontMetrics(); //因为ascent在baseline之上,所以ascent为负数。descent+ascent为负数,所以是减而不是加 float baseLine = height*1.0f/2 - (metrics.descent+metrics.ascent)/2;
drawText的全部代码如下:
//画进度文字 String text = ((int)(ratio*100))+"%"; //获得文字的宽度 float textWidth = mTextPaint.measureText(text); Paint.FontMetrics metrics = mTextPaint.getFontMetrics(); // descent+ascent è un numero negativo, quindi è una sottrazione e non una somma float baseLine = height*1.0f/2 - (metrics.descent+metrics.ascent)/2; mPaintCanvas.drawText(text, width/2-textWidth/2, baseLine, mTextPaint);
3. Poiché devono essere considerati i padding, ricorda di traslare il canvas in onDraw a (getPaddingLeft(), getPaddingTop()).
canvas.translate(getPaddingLeft(), getPaddingTop()); canvas.drawBitmap(mBitmap, 0, 0, null);
Alla fine, ricorda di disegnare il bitmap personalizzato sul canvas di onDraw. Arrivati qui, ho completato la barra di progresso dell'effetto dell'acqua in crescita personalizzata.
Sommario
Questo è tutto il contenuto dell'articolo, speriamo che il contenuto di questo articolo possa aiutare la tua apprendimento o lavoro in qualche modo. Se hai domande, puoi lasciare un messaggio per discutere.
Dichiarazione: il contenuto di questo articolo è stato tratto da Internet, il copyright è di proprietà del rispettivo proprietario, il contenuto è stato contribuito e caricato autonomamente dagli utenti di Internet, il sito web non detiene i diritti di proprietà, non è stato editato manualmente e non assume responsabilità per le relative responsabilità legali. Se trovi contenuti sospetti di copyright, invia una e-mail a notice#oldtoolbag.com (sostituisci # con @) per segnalare il problema e fornire prove pertinenti. Una volta verificata, il sito web eliminerà immediatamente il contenuto sospetto di violazione del copyright.