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
4.Maintivity
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
5效果图: