1. 程式人生 > >Android 仿UC瀏覽器三點載入效果

Android 仿UC瀏覽器三點載入效果

1.

import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.net.Uri;
import
android.util.DisplayMetrics; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.widget.ListAdapter; import android.widget.ListView; public class MeasureUtil { /** * 應用程式App區域寬高等尺寸獲取,最好在Activity的onWindowFocusChanged ()方法或者之後調運 */ public
static Rect getAppAreaRect(Activity context) { Rect rect = new Rect(); context.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect); return rect; } /** * 獲取狀態列高度,最好在Activity的onWindowFocusChanged ()方法或者之後調運 */ public static int getStatusBarHeight
(Activity context) { Rect rect = new Rect(); context.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect); return rect.top; } /** * View佈局區域寬高等尺寸獲取,最好在Activity的onWindowFocusChanged ()方法或者之後調運 */ public static Rect getContentViewRect(Activity context) { Rect rect = new Rect(); context.getWindow().findViewById(Window.ID_ANDROID_CONTENT).getDrawingRect(rect); return rect; } /** * 獲取狀態列的高度 * * @param context 上下文 * @return 狀態列高度 */ public static int getStatusBarHeight(Context context) { int result = 0; int resourceId = context.getResources().getIdentifier( "status_bar_height", "dimen", "android"); if (resourceId > 0) { result = context.getResources().getDimensionPixelSize(resourceId); } return result; } public static int getToolbarHeight(Context context) { final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes( new int[]{R.attr.actionBarSize}); int toolbarHeight = (int) styledAttributes.getDimension(0, 0); styledAttributes.recycle(); return toolbarHeight; } /** * 可將當前view儲存為圖片的工具 * * @param v * @return */ public static Bitmap createViewBitmap(View v) { Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); v.draw(canvas); return bitmap; } /** * 可將當前view儲存為圖片的工具 * * @param view * @return */ public static Bitmap convertViewToBitmap(View view) { view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); view.buildDrawingCache(); Bitmap bitmap = view.getDrawingCache(); return bitmap; } /** * 獲取app內部資源的uri,用於fresco設定本地圖片 * * @param resId * @param packageName * @return */ public static Uri getResourceUri(int resId, String packageName) { return Uri.parse("res://" + packageName + "/" + resId); } /** * 獲取螢幕尺寸 * * @param activity Activity * @return 螢幕尺寸畫素值,下標為0的值為寬,下標為1的值為高 */ public static int[] getScreenSize(Activity activity) { DisplayMetrics metrics = new DisplayMetrics(); activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); return new int[]{metrics.widthPixels, metrics.heightPixels}; } public static int getScreenWidth(Activity activity) { DisplayMetrics metrics = new DisplayMetrics(); activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); return metrics.widthPixels; } public static int getScreenHeight(Activity activity) { DisplayMetrics metrics = new DisplayMetrics(); activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); return metrics.heightPixels; } /** * 根據手機的解析度從 dp 的單位 轉成為 px(畫素) */ public static int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } /** * 根據手機的解析度從 px(畫素) 的單位 轉成為 dp */ public static int px2dip(Context context, float pxValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / scale + 0.5f); } /** * 將px值轉換為sp值,保證文字大小不變 * * @param pxValue * @param context (DisplayMetrics類中屬性scaledDensity) * @return */ public static int px2sp(Context context, float pxValue) { final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return (int) (pxValue / fontScale + 0.5f); } /** * 將sp值轉換為px值,保證文字大小不變 * * @param spValue * @param context (DisplayMetrics類中屬性scaledDensity) * @return */ public static int sp2px(Context context, float spValue) { final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return (int) (spValue * fontScale + 0.5f); } /** * 動態測量listview item的高度 * * @param listView */ public static void setListViewHeightBasedOnChildren(ListView listView) { ListAdapter listAdapter = listView.getAdapter(); if (listAdapter == null) { // pre-condition return; } int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom(); for (int i = 0; i < listAdapter.getCount(); i++) { View listItem = listAdapter.getView(i, null, listView); if (listItem instanceof ViewGroup) { listItem.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } listItem.measure(0, 0); totalHeight += listItem.getMeasuredHeight(); } ViewGroup.LayoutParams params = listView.getLayoutParams(); params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); listView.setLayoutParams(params); } /** * 動態測量listview item的高度 * * @param listView */ public static void setListViewHeightBasedOnChildren1(ListView listView) { ListAdapter listAdapter = listView.getAdapter(); if (listAdapter == null) { return; } int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.AT_MOST); int totalHeight = 0; View view = null; for (int i = 0; i < listAdapter.getCount(); i++) { view = listAdapter.getView(i, view, listView); if (i == 0) { view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, ViewGroup.LayoutParams.WRAP_CONTENT)); } view.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED); totalHeight += view.getMeasuredHeight(); } ViewGroup.LayoutParams params = listView.getLayoutParams(); params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); listView.setLayoutParams(params); listView.requestLayout(); } public static int[] getImageRealSize(Context context, int id) { BitmapFactory.Options options = new BitmapFactory.Options(); /** * 最關鍵在此,把options.inJustDecodeBounds = true; * 這裡再decodeFile(),返回的bitmap為空,但此時呼叫options.outHeight時,已經包含了圖片的高了 */ options.inJustDecodeBounds = true; Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), id, options); int size[] = new int[2]; size[0] = options.outWidth; size[1] = options.outHeight; return size; } public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // 源圖片的高度和寬度 final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { // 計算出實際寬高和目標寬高的比率 final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); // 選擇寬和高中最小的比率作為inSampleSize的值,這樣可以保證最終圖片的寬和高 // 一定都會大於等於目標的寬和高。 inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } return inSampleSize; } public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // 第一次解析將inJustDecodeBounds設定為true,來獲取圖片大小 final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // 呼叫上面定義的方法計算inSampleSize值 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 使用獲取到的inSampleSize值再次解析圖片 options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } }

2.

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.View;

public class ThreePointLoadingView extends View {

    // 畫筆
    private Paint mBallPaint;
    // 寬度
    private int mWidth;
    // 高度
    private int mHeight;
    // 圓之間的距離
    private float mSpace;
    // 圓的半徑
    private float mBallRadius;
    // 三個圓合起來的距離(包括間距)
    private float mTotalLength;
    // A圓心的x座標
    private float mABallX;
    // A圓心的y座標
    private float mABallY;
    // B圓心的x座標
    private float mBBallX;
    // B圓心的y座標
    private float mBBallY;
    // C圓心的x座標
    private float mCBallX;
    // C圓心的y座標
    private float mCBallY;

    // 圓心移動的距離
    private float mMoveLength;

    // A圓心做二階貝塞爾曲線的起點、控制點、終點
    private PointF mABallP0;
    private PointF mABallP1;
    private PointF mABallP2;

    // A圓心貝塞爾曲線運動時的座標
    private float mABallazierX;
    private float mABallazierY;

    // 值動畫
    private ValueAnimator mAnimator;

    // 值動畫產生的x方向的偏移量
    private float mOffsetX = 0;
    // 根據mOffsetX算得的y方向的偏移量
    private float mOffsetY;

    // A圓的起始透明度
    private int mABallAlpha = 255;
    // B圓的起始透明度
    private int mBBallAlpha = (int) (255 * 0.8);
    // C圓的起始透明度
    private int mCBallAlpha = (int) (255 * 0.6);


    public ThreePointLoadingView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {

        mBallPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);

//設定顏色
        mBallPaint.setColor(ContextCompat.getColor(getContext(), R.color.material_deep_orange_a200));

        mBallPaint.setStyle(Paint.Style.FILL);

        mABallP0 = new PointF();
        mABallP1 = new PointF();
        mABallP2 = new PointF();

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        // 考慮padding值
        mWidth = measureSize(widthMeasureSpec, MeasureUtil.dip2px(getContext(), 180)) + getPaddingLeft() + getPaddingRight();
        mHeight = measureSize(heightMeasureSpec, MeasureUtil.dip2px(getContext(), 180)) + getPaddingTop() + getPaddingBottom();

        setMeasuredDimension(mWidth, mHeight);

        // 間距為寬度10分之一
        mSpace = mWidth * 1.0f / 20;

        // 半徑為寬度50分之一
        mBallRadius = mWidth * 1.0f / 50;

        // 總的長度為三個圓直徑加上之間的間距
        mTotalLength = mBallRadius * 6 + mSpace * 2;

        // 兩個圓圓心的距離
        mMoveLength = mSpace + mBallRadius * 2;

        // A圓心起始座標,同時貝塞爾曲線的起始座標也是這個
        mABallazierX = mABallX = (mWidth - mTotalLength) / 2 + mBallRadius;
        mABallazierY = mABallY = mHeight / 2;

        // A圓心起始點,控制點,終點
        mABallP0.set(mABallX, mABallY);
        mABallP1.set(mABallX + mMoveLength / 2, mABallY - mMoveLength / 2);
        mABallP2.set(mBBallX, mBBallY);

        // B圓心的起始座標
        mBBallX = (mWidth - mTotalLength) / 2 + mBallRadius * 3 + mSpace;
        mBBallY = mHeight / 2;

        // C圓心的起始座標
        mCBallX = (mWidth - mTotalLength) / 2 + mBallRadius * 5 + mSpace * 2;
        mCBallY = mHeight / 2;

    }

    @Override
    protected void onDraw(Canvas canvas) {

        // 根據x方向偏移量求出y方向偏移量
        mOffsetY = (float) Math.sqrt(mMoveLength / 2 * mMoveLength / 2 - (mMoveLength / 2 - mOffsetX) * (mMoveLength / 2 - mOffsetX));

        // 繪製B圓
        mBallPaint.setAlpha(mBBallAlpha);
        canvas.drawCircle(mBBallX - mOffsetX,
                (float) (mBBallY + mOffsetY),
                mBallRadius,
                mBallPaint);

        // 繪製C圓
        mBallPaint.setAlpha(mCBallAlpha);
        canvas.drawCircle(mCBallX - mOffsetX,
                (float) (mCBallY - mOffsetY),
                mBallRadius,
                mBallPaint);

        // 繪製A圓
        mBallPaint.setAlpha(mABallAlpha);
        canvas.drawCircle(mABallazierX, mABallazierY, mBallRadius, mBallPaint);

        if (mAnimator == null) {
            // 啟動值動畫
            startLoading();
        }

    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        // 銷燬view時取消動畫,避免記憶體洩露
        mAnimator.cancel();
    }

    // 開啟值動畫
    private void startLoading() {

        // 範圍在0到圓心移動的距離,這個是以B圓到A圓位置為基準的
        mAnimator = ValueAnimator.ofFloat(0, mMoveLength);

        // 設定監聽
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

                // B圓和C圓對應的X的偏移量
                mOffsetX = (float) animation.getAnimatedValue();

                float fraction = animation.getAnimatedFraction();

                // B移動到A,透明度變化255*0.8->255
                mBBallAlpha = (int) (255 * 0.8 + 255 * fraction * 0.2);
                // C移動到B,透明度變化255*0.6->255*0.8
                mCBallAlpha = (int) (255 * 0.6 + 255 * fraction * 0.2);
                // A移動到C,透明度變化255->255*0.6
                mABallAlpha = (int) (255 - 255 * fraction * 0.4);

                // A圓的分段二階貝塞爾曲線的處理
                if (fraction < 0.5) {
                    // fraction小於0.5時,為A到B過程的情況

                    // 乘以2是因為貝塞爾公式的t範圍在0到1
                    fraction *= 2;

                    // 設定當前情況的起始點、控制點、終點
                    mABallP0.set(mABallX, mABallY);
                    mABallP1.set(mABallX + mMoveLength / 2, mABallY - mMoveLength / 2);
                    mABallP2.set(mBBallX, mBBallY);

                    // 代入貝塞爾公式得到貝塞爾曲線過程的x,y座標
                    mABallazierX = getBazierValue(fraction, mABallP0.x, mABallP1.x, mABallP2.x);
                    mABallazierY = getBazierValue(fraction, mABallP0.y, mABallP1.y, mABallP2.y);
                } else {
                    // fraction大於等於0.5時,為A到B過程之後,再從B到C過程的情況

                    // 減0.5是因為t要從0開始變化
                    fraction -= 0.5;
                    // 乘以2是因為貝塞爾公式的t範圍在0到1
                    fraction *= 2;

                    // 設定當前情況的起始點、控制點、終點
                    mABallP0.set(mBBallX, mBBallY);
                    mABallP1.set(mBBallX + mMoveLength / 2, mBBallY + mMoveLength / 2);
                    mABallP2.set(mCBallX, mCBallY);

                    // 代入貝塞爾公式得到貝塞爾曲線過程的x,y座標
                    mABallazierX = getBazierValue(fraction, mABallP0.x, mABallP1.x, mABallP2.x);
                    mABallazierY = getBazierValue(fraction, mABallP0.y, mABallP1.y, mABallP2.y);
                }

                // 強制重新整理
                postInvalidate();

            }
        });
        // 動畫無限模式
        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
        // 時長1秒
        mAnimator.setDuration(1000);
        // 延遲0.5秒執行
        mAnimator.setStartDelay(500);
        // 開啟動畫
        mAnimator.start();

    }

    /**
     * 二階貝塞爾公式:B(t)=(1-t)^2*P0+2*t*(1-t)*P1+t^2*P2,(t∈[0,1])
     */
    private float getBazierValue(float fraction, float p0, float p1, float p2) {
        return (1 - fraction) * (1 - fraction) * p0 + 2 * fraction * (1 - fraction) * p1 + fraction * fraction * p2;
    }

    // 測量尺寸
    private int measureSize(int measureSpec, int defaultSize) {

        final int mode = MeasureSpec.getMode(measureSpec);
        final int size = MeasureSpec.getSize(measureSpec);

        if (mode == MeasureSpec.EXACTLY) {
            return size;
        } else if (mode == MeasureSpec.AT_MOST) {
            return Math.min(size, defaultSize);
        }

        return size;
    }

}

3.main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <example.threepointloadingview.ThreePointLoadingView
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

4.Maintivity

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

5效果圖:

這裡寫圖片描述