Android 仿UC瀏覽器三點載入效果
阿新 • • 發佈:2019-02-20
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效果圖: