[置頂](表示電量機能を加える)魅族、華為、小米電池の航続管理ソフトウェアを模倣し、動的な水の波紋が転がる円形の小球View


一、効果図:
図1は、プロジェクトの効果を示す図です.
図2は、このView単独の効果図である.
二、使用方法:
1は、カスタムビューなので、attrs.xmlに次の属性定義を追加する必要があります.
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <attr name="batteryLevel" format="float" />
    <attr name="radius" format="integer" />
    <attr name="flowRadius" format="dimension" />
    <attr name="textColor" format="color" />
    <attr name="textSize" format="dimension" />
    <attr name="flowViewColor" format="color" />

    <declare-styleable name="BatteryView">
        <attr name="batteryLevel" />
        <attr name="radius" />
        <attr name="flowRadius" />
        <attr name="textColor" />
        <attr name="textSize" />
        <attr name="flowViewColor"/>
    </declare-styleable>

</resources>
2レイアウトファイルに導入:
xmlns:zhangxutong="http://schemas.android.com/apk/res-auto"
<mcxtzhang.weixin521.help.BatteryView
    android:background="@drawable/battery_view_bg_circle"
    android:id="@+id/batteryView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:padding="10dp"
    zhangxutong:flowViewColor="#ffaaffaa"/>
以上のカスタム属性は、波が転がるボールの色をカスタマイズする必要がある場合はflowViewColorに入力します.そうしないと、デフォルトの属性を完全に使用できます.既定のアトリビュートは、図1のように半透明の白です.
3、カスタムViewを定義する:
コードは次の図のとおりです:詳細なコメントがあります:
package mcxtzhang.weixin521.help;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

import mcxtzhang.weixin521.R;
import mcxtzhang.weixin521.utils.TipsUtils;


/**  * @author zhangxutong  */ public class BatteryView extends View {
    private Context mContext;
    /**  *       */  private float level;
    /**  *       */  private int radius;
    /**  *        (  ,     -padding   )  */  private int flowRadius;
    /**  *          */  private int flowColor;
    /**  *             */  private int textColor;
    /**  *            */  private int textSize;
    /**  *    X     */  private float FAI;
    /**  *        */  private float A;
    /**  *         */  private float W;
    /**  *    Y     */  private float K;

    //     
    /**  *   (  )  */  private Paint mPaint;
    /**  *      ,    */  private Paint mTextPaint;
    /**  *        Path  */  private Path mPath;
    /**  *        */  private PaintFlagsDrawFilter mDrawFilter;
    /**  *         */  private PorterDuffXfermode mPorterDuffXfermode;
    /**  *         */  private Bitmap mBitmap;
    /**  *       */  private int mWidth;
    /**  *       */  private int mHeight;
    /**  *          */  private int mCenterX;
    /**  *          */  private int mCenterY;
    /**  *         */  private Rect mSrcRect;
    /**  *           */  private Rect mDestRect;
    /**  *                     */  private Rect mTextRect;
    /**  *                    */  private StringBuilder sb;

    /**  *            ,      */  interface OnLevelChangeListener {
        int onLevelChange();
    }

    OnLevelChangeListener mOnLevelChangeListener;

    public void setOnLevelChangeListener(
            OnLevelChangeListener onLevelChangeListener) {
        mOnLevelChangeListener = onLevelChangeListener;
    }

    /**  *         */  private BroadcastReceiver mBatteryChangeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
                LogI("onReceiver BatteryChanged");
                level = intent.getIntExtra("level", -1);
                int curScale = intent.getIntExtra("scale", -1);
                sb.delete(0, sb.length());
                sb.append("" + (int) level * 100 / curScale).append("%");
                //  AK,         ,       ,      
                setA();
                //setK();
            }
        }
    };

    /**  *   Y   K,          ,       ,      (  )  */  private void setK() {
        // K:-5~+5 , level :0~100
        /*float k = 20f/100f;
        K = k*level;*/
    }

    /**  *     A,A            , level = 50 ,    ,     , 0 100     0  */  private void setA() {
        //y = ax;
        /*float k = 10f / 50f ;
        A = level<50? k*level:-k*(level-100);*/
        //y  = ax2+bx+c ( 0,3) (50,10 )( 100,3)
        if (level == 0 || level == 100) {
            A = 0;
        } else {
            float a = -7f / 2500;
            float b = -100 * a;
            float c = 3;
            A = (float) (a * Math.pow(level, 2) + b * level + c);
        }

        //LogI("A:"+A);

    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        LogI("onAttachedToWindow");
        //onAttachedToWindow  onDraw   ,           
        mContext.registerReceiver(mBatteryChangeReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        LogI("onDetachedFromWindow");
        //      ,       
        mContext.unregisterReceiver(mBatteryChangeReceiver);
    }


    public BatteryView(Context context) {
        this(context, null);
    }

    public BatteryView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BatteryView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        LogI("inital func");
        init();
        //      attr        
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
                R.styleable.BatteryView, defStyleAttr, 0);
        int n = a.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case R.styleable.BatteryView_batteryLevel:
                    level = a.getFloat(attr, 0);
                    break;
                case R.styleable.BatteryView_flowRadius:
                    flowRadius = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 150, getResources().getDisplayMetrics()));
                    break;
                case R.styleable.BatteryView_radius:
                    radius = a.getInt(attr, getWidth());
                    break;
                case R.styleable.BatteryView_textColor:
                    textColor = a.getColor(attr, Color.WHITE);
                    break;
                case R.styleable.BatteryView_textSize:
                    textSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
                            TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
                    break;
                case R.styleable.BatteryView_flowViewColor:
                    TipsUtils.showToast(context,"flowViewColor");
                    flowColor = a.getColor(attr,0x88FFFFFF);
                    break;
            }
        }
        a.recycle();

        initPaint();

        //               ,  FAI, X     ,            
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    changeFAI();
                    try {
                        Thread.sleep(60);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    postInvalidate();
                }
            }

            private void changeFAI() {
                FAI += 0.2;
            }
        }.start();
    }

    /**  *          */  private void init() {
        level = 0;
        radius = getWidth();
        flowRadius = 150;
        flowColor = 0x88FFFFFF;
        FAI = 0;
        A = 9;
        W = 0.75f;
        K = 0;
    }
    /**  *            */  private void initPaint(){
        //        
        //    
        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        //       ,   DST,   SRC,   ,  DST
        mPorterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.FILL);
        mPath = new Path();
        //    
        mPaint.setDither(true);
        //       
        mPaint.setFilterBitmap(true);
        //           
        mPaint.setColor(flowColor);
        //        
        mBitmap = ((BitmapDrawable) getResources().getDrawable(
                R.drawable.battery_view_bg_round)).getBitmap();
        //        Paint Rect
        mTextPaint = new Paint();
        //      ,  ,  ,  
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
      /*Typeface tf = Typeface.createFromAsset(mContext.getAssets(), "fonts/Roboto-Thin.ttf");
      mTextPaint.setTypeface(tf);*/
        mTextRect = new Rect();
        sb = new StringBuilder();
    }

    //private int temp = 0;
    @Override
    protected void onDraw(Canvas canvas) {
        //LogI("flowRadius" + flowRadius + "  getPaddingRight:" + getPaddingRight());
        //if(temp++==0)
        //LogI("onDraw");
        //  canvas     
        canvas.setDrawFilter(mDrawFilter);
        //       ,      
        int sc = canvas.saveLayer(0 + getPaddingLeft(), 0 + getPaddingTop(), mWidth - getPaddingRight(), mHeight - getPaddingBottom(), null,
                Canvas.ALL_SAVE_FLAG);
        //     path
        mPath.reset();
/*    level = 0;
      setA();
      //setK();
*/
        //         Y = A sin(wx+FAI)+k,         Path.quadTo()  (       ) ,      。
        for (int x = mDestRect.left; x < mDestRect.right; x++) {
            float y = (float) (A * Math.sin(Math.PI / 250 * W * x + FAI) + K + (mDestRect.bottom - mDestRect.top)
                    * 1.0f / 100 * (100 - level));
            if (x == mDestRect.left) {
                mPath.moveTo(x, y);
            }
            mPath.quadTo(x, y, x + 1, y);
        }
        // Path    ,    
        mPath.lineTo(mDestRect.right, mDestRect.bottom);
        mPath.lineTo(mDestRect.left, mDestRect.bottom);
        mPath.close();
        //      (       ,     ) (DST)
        canvas.drawPath(mPath, mPaint);
        //      (      )
        mPaint.setXfermode(mPorterDuffXfermode);
        //          (SRC)
        canvas.drawBitmap(mBitmap, mSrcRect, mDestRect, mPaint);
        //       null
        mPaint.setXfermode(null);
        //         bitmap,        (  )
        canvas.restoreToCount(sc);
        //canvas.restore();    

        //                                  ,    。
      /*mTextPaint.setTextSize(textSize);
      mTextPaint.setColor(textColor);
      mTextPaint.getTextBounds(sb.toString(), 0, sb.toString().length(), mTextRect);
      //      
      canvas.drawText(sb.toString(), mCenterX, mCenterY,mTextPaint);
      mTextPaint.setTextSize(24);*/

    }

    /**  *    onSizeChanged       view   ,         */  @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        LogI("onSizeChanged:" + getPaddingLeft());
        mWidth = w;
        mHeight = h;
        mCenterX = w / 2;
        mCenterY = h / 2;
        mSrcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
      /*mDestRect = new Rect(mCenterX - flowRadius, mCenterY - flowRadius,
            mCenterX + flowRadius, mCenterY + flowRadius);*/
        //            View padding,
        mDestRect = new Rect(0 + getPaddingLeft(), 0 + getPaddingTop(), mWidth - getPaddingRight(), mHeight - getPaddingBottom());
        //mDestRect = new Rect(mWidth/2-flowRadius, mHeight/2-flowRadius,mWidth/2+flowRadius ,  mHeight/2+flowRadius);
    }

    //    wrap_content,    AT_MOST         ,    wrap_content      150dp
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        LogI("onMeasure");
        setMeasuredDimension(measuredWidth(widthMeasureSpec), measuredHeight(heightMeasureSpec));
    }

    private int measuredHeight(int heightMeasureSpec) {
        //   weight     150dp
        int measuredHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 150, getResources().getDisplayMetrics());
        //          
        int specMode = MeasureSpec.getMode(heightMeasureSpec);
        int specSize = MeasureSpec.getSize(heightMeasureSpec);
        //        Match_parent
        if (MeasureSpec.EXACTLY == specMode) {
            measuredHeight = specSize;
        } else { //AT_MOST:wrap_content  ,UNSPECIFIED    。   View       
            //            
            measuredHeight = Math.min(specSize, measuredHeight);
        }
        return measuredHeight;
    }

    private int measuredWidth(int widthMeasureSpec) {
        int measuredWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 150, getResources().getDisplayMetrics());
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
        if (MeasureSpec.EXACTLY == specMode) {
            measuredWidth = specSize;
        } else {
            measuredWidth = Math.min(specSize, measuredWidth);
        }
        return measuredWidth;
    }

    private void LogI(String msg) {
        android.util.Log.i("zhangxutong/BatteryView", "" + msg);
    }

}
ポイントはonDrawでpath.quaTo()メソッドを用いて、ベッセル曲線(滑らかな曲線)を描き、正弦関数曲線図を描くことです.波の効果をシミュレートします.
波を動的にスクロールさせる方法については、スレッドを開き、正弦波曲線関数のFAI(横座標のオフセット量)を動的に変更すると、波がスクロールします.
========================================
ソースコードとクイック使用説明ドキュメントを添付し、
すべてのリソースファイルが含まれています.
http://download.csdn.net/detail/zxt0601/9421642