AndroidカスタムViewは水面上昇効果を実現します。

12855 ワード

実現効果は以下の通りです。

実現の考え方:
1、円の水面上昇効果はどうやって実現しますか?Paintを利用したsetXfermode属性はPorterDuff.Mode.SRC_です。IN進捗のある長方形と円を描いて交差させます。
2、どのように水の波紋効果がありますか?ベジェ曲線を利用して、波のピークを動的に変化させ、「進度が増すにつれて、水波紋が小さくなる効果」を実現します。
話は多くなくて、コードを見ます。
まず、ユーザー定義属性の値ですが、どのような属性値がありますか?
円の背景色:cirle_カラー、進捗の色:プログレスカラー、進捗表示テキストの色:text_カラー、進捗テキストのサイズ:text_size、最後の一つがあります。波紋の最大高さ:ripple_topheigh

<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:WaterPrograess Viewの一部コードです。
メンバー変数

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;
 //       
 private int mTextColor;
 private int mTextSize = 18;
 //    ,             ,        
 private int mTargetProgress = 50;

 //         
 private GestureDetector mGestureDetector;
}
カスタム属性の値を取得:

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();

}
コンストラクタを定義します。

// 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);

}
mProgressPaint.setXfermode方法コード:

@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
 //   ,       View   ,       MeasureSpec.EXACTLY
 int width = MeasureSpec.getSize(widthMeasureSpec); 
 int height = MeasureSpec.getSize(heightMeasureSpec); 
 setMeasuredDimension(width,height); 

 //   Bitmap,    drawCircle,drawPath,drawText draw  bitmap   canvas ,     bitmap   onDraw   canvas ,
 //   bitmap width,height    left,top,right,bottom padding
 mBitmap = Bitmap.createBitmap(width-getPaddingLeft()-getPaddingRight(),height- getPaddingTop()-getPaddingBottom(), Bitmap.Config.ARGB_8888); 
 mPaintCanvas = new Canvas(mBitmap);
}
次にコア部分であるonDrawのコードです。まずCircule、プログレスバー、プログレステキストdrawをカスタムcanvasのbitmapに送り、このbitmap drawをonDrawメソッドのcanvasに送ります。drawCirculeとdrawTextはあまり難しくないはずです。ポイントは進行状況を描くことです。どうやって描けばいいですか?水の波紋効果があり、曲線があるなら、ドラフトパスを使います。
drawPathの流れは以下の通りです。

ここで、ratioのコードは以下の通りです。つまり、ratioは現在の進捗度の全体進捗率を占めます。

float ratio = getProgress()*1.0f/getMax();
座標はB点から下と右に延びるので、A点の座標は(width,(1−ratio)*height)であり、widthはbitmapの幅であり、heightはbitmapの高さである。まずmProgress Path.moveToをA点にして、A点から時計回りにPathの各キーポイントを決定します。図のように、コードは以下の通りです。

int rightTop = (int) ((1-ratio)*height);
mProgressPath.moveTo(width,rightTop);
mProgressPath.lineTo(width,height);
mProgressPath.lineTo(0,height);
mProgressPath.lineTo(0,rightTop);
このようなmPrograess PathはもうライントップでC点に着きました。A点とC点の間に水波紋効果を形成する必要があります。A点とC点の間にベジェ曲線を描く必要があります。

ピークの最高点を10とすると、波長は40で、onMeasure()段のような曲線が必要です。曲線のコードは以下の通りです。

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);
水面の上昇と波紋効果のある進行形が描けます。しかし、水面の上昇に伴い、目標の進捗に近づくほど、水面の波紋は小さくなるべきであり、10の抽出を変数としてmRippleTopなどの初期時のピークの最大値と定義し、その後topを目標の進捗に合わせて目標の進捗に近づいていく時の曲線のリアルタイム波の値と定義します。目標の進捗があるから、現在の進捗状況が目標の進捗に近づく過程で、水面がだんだん平面に向かう効果がある:

float top = (mTargetProgress-getProgress())*1.0f/mTargetProgress* mRippleTop;
したがって、drawPathのコード更新は以下の通りである。

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); 
}
これで、水面の上昇の進行状況が本当に実現できます。
図中をダブルクリックすると水面が0%から目標の進捗まで上昇しますが、クリックすると水面が目標の進捗度でわき返る効果はどうなりますか?
まず、ダブルクリックの効果の実現について説明します。これは簡単で、Handlerを定義します。ダブルクリックするとwidth*1.0f/40、一定時間おきにhandler.postDelayed(runnable,time) 、runnableのprogress+1において、現在のプログレスがmTarget Progessに達するまで、どんどん更新されます。
コードは以下の通りです

/**
 *       
 */
private void startDoubleTapAnimation() { 
 setProgress(0); 
 doubleTapHandler.postDelayed(doubleTapRunnable,60);
}

private Handler doubleTapHandler = new Handler(){ 
 @Override 
 public void handleMessage(Message msg) {  
 super.handleMessage(msg); 
 }
};

//      , 60ms      
private Runnable doubleTapRunnable = new Runnable() { 
 @Override 
 public void run() {  
 if(getProgress() < mTargetProgress) {   
  invalidate();   
  setProgress(getProgress()+1);   
  doubleTapHandler.postDelayed(doubleTapRunnable,60);  
 } else {   
  doubleTapHandler.removeCallbacks(doubleTapRunnable);  
 } 
 }
};
ダブルクリック効果が実現しましたが、クリック効果はどうなりますか?クリックすると水面が一時的に沸き、水面の波紋がだんだん小さくなり、水面が平らになります。mSingleTapAnimationCount変数を水面のロールアップ回数として定義し、ダブルクリック時の処理のように、Handlerを一定時間おきに更新画面を送信するメッセージを定義し、invalidate()を交互に初期時のピークをプラスにして、水面のロールアップ効果を実現します。
コアコードは以下の通りです。

private void startSingleTapAnimation() { 
 isSingleTapAnimation = true; 
 singleTapHandler.postDelayed(singleTapRunnable,200);
}

private Handler singleTapHandler = new Handler(){ 
 @Override 
 public void handleMessage(Message msg) {  
 super.handleMessage(msg); 
 }
};

//      , 200ms      
private Runnable singleTapRunnable = new Runnable() { 
 @Override 
 public void run() {  
 if(mSingleTapAnimationCount > 0) {   
  invalidate();   
  mSingleTapAnimationCount--;   
  singleTapHandler.postDelayed(singleTapRunnable,200);  
 } else {   
  singleTapHandler.removeCallbacks(singleTapRunnable);  
 //            
  isSingleTapAnimation = false;   
 //           50 
  mSingleTapAnimationCount = 50;  
 } 
 }
};
onDrawのコードは対応して変更されます。クリック時とダブルクリック時のdrawPathの曲線部分の描画ロジックが異なるので、変数isSingleTapAnimationを定義します。アニメーションをクリックするかダブルクリックするかを区別します。
変更後のコードは以下の通りです。

//   
mProgressPath.reset();
//      draw path
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));
//    animation  
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); 
 }
} else { 
 //  animation  ,       , mRippleTop  2 
 //             ,           
 float top = (mSingleTapAnimationCount*1.0f/50)*10; 
 //         
 if(mSingleTapAnimationCount%2==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);   
 } 
 } 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); 
 } 
 }
}
mProgressPath.close();
mPaintCanvas.drawPath(mProgressPath,mProgressPaint);
基本的に重要なコードとコアロジックとコードは上にあります。
注意点:
1、drawCirculeの場合はpaddingを考慮すると、circeleの幅と高さはget Widthと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   ,        
float baseLine = height*1.0f/2 - (metrics.descent+metrics.ascent)/2;
mPaintCanvas.drawText(text,width/2-textWidth/2,baseLine,mTextPaint);
3、paddingに配慮するので、onDrawのcanvas tranlateをmSingleTapAnimationCount-- 箇所にしてください。

canvas.translate(getPaddingLeft(),getPaddingTop());
canvas.drawBitmap(mBitmap,0,0,null);
最後にカスタムbitmap drawをonDrawのcanvasに登録してください。ここまでカスタマイズした水面の上昇効果の進捗状況は書き終わりました。
締め括りをつける
以上はこの文章の内容の全部です。本文の内容は皆さんの学習や仕事に一定の助けをもたらしてほしいです。もし疑問があれば、メッセージを残して交流してもいいです。