package com.d.lib.common.util; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; import android.app.ActivityManager; import android.content.Context; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.LightingColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.ColorInt; import android.support.annotation.IdRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.util.TypedValue; import android.view.TouchDelegate; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewStub; import android.view.ViewTreeObserver; import android.view.Window; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.DecelerateInterpolator; import android.view.animation.TranslateAnimation; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * @author cginechen * @date 2016-03-17 */ public class ViewHelper { // Copy from View.generateViewId for API <= 16 private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1); @NonNull public static <T> T findView(@NonNull Activity activity, @IdRes int id) { T view = (T) activity.findViewById(id); return view; } @NonNull public static <T> T findView(@NonNull View root, @IdRes int id) { T view = (T) root.findViewById(id); return view; } public static void setOnClick(@NonNull Activity activity, @Nullable View.OnClickListener l, @IdRes int... ids) { for (int id : ids) { activity.findViewById(id).setOnClickListener(l); } } public static void setOnClick(@NonNull View root, @Nullable View.OnClickListener l, @IdRes int... ids) { for (int id : ids) { root.findViewById(id).setOnClickListener(l); } } public static void setVisibility(@NonNull Activity activity, int visibility, @IdRes int... ids) { for (int id : ids) { activity.findViewById(id).setVisibility(visibility); } } public static void setVisibility(@NonNull View root, int visibility, @IdRes int... ids) { for (int id : ids) { root.findViewById(id).setVisibility(visibility); } } public static void setVisibility(int visibility, View... views) { for (View v : views) { v.setVisibility(visibility); } } public static String getRunningActivityName(Context context) { if (context == null) { return null; } ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); String runningActivity = activityManager.getRunningTasks(1).get(0).topActivity .getClassName(); return runningActivity; } /** * 获取activity的根view * * @param activity * @return */ public static View getActivityRoot(Activity activity) { return ((ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT)).getChildAt(0); } public static void removeOnGlobalLayoutListener(View view, ViewTreeObserver.OnGlobalLayoutListener victim) { if (view == null || victim == null) { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { view.getViewTreeObserver().removeOnGlobalLayoutListener(victim); } else { view.getViewTreeObserver().removeGlobalOnLayoutListener(victim); } } /** * 触发window的insets的广播,使得view的fitSystemWindows得以生效 * * @param window */ @SuppressWarnings("deprecation") public static void requestApplyInsets(Window window) { if (Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT < 21) { window.getDecorView().requestFitSystemWindows(); } else if (Build.VERSION.SDK_INT >= 21) { window.getDecorView().requestApplyInsets(); } } /** * 扩展点击区域的范围 * * @param view 需要扩展的元素,此元素必需要有父级元素 * @param expendSize 需要扩展的尺寸(以sp为单位的) */ public static void expendTouchArea(final View view, final int expendSize) { if (view != null) { final View parentView = (View) view.getParent(); parentView.post(new Runnable() { @Override public void run() { Rect rect = new Rect(); view.getHitRect(rect); //如果太早执行本函数,会获取rect失败,因为此时UI界面尚未开始绘制,无法获得正确的坐标 rect.left -= expendSize; rect.top -= expendSize; rect.right += expendSize; rect.bottom += expendSize; parentView.setTouchDelegate(new TouchDelegate(rect, view)); } }); } } @SuppressWarnings("deprecation") @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public static void setBackgroundKeepingPadding(View view, Drawable drawable) { int[] padding = new int[]{view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()}; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { view.setBackground(drawable); } else { view.setBackgroundDrawable(drawable); } view.setPadding(padding[0], padding[1], padding[2], padding[3]); } @SuppressWarnings("deprecation") public static void setBackgroundKeepingPadding(View view, int backgroundResId) { setBackgroundKeepingPadding(view, view.getResources().getDrawable(backgroundResId)); } public static void setBackgroundColorKeepPadding(View view, @ColorInt int color) { int[] padding = new int[]{view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()}; view.setBackgroundColor(color); view.setPadding(padding[0], padding[1], padding[2], padding[3]); } /** * 对 View 的做背景闪动的动画 */ @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) public static void playBackgroundBlinkAnimation(final View v, @ColorInt int bgColor) { if (v == null) { return; } int[] alphaArray = new int[]{0, 255, 0}; playViewBackgroundAnimation(v, bgColor, alphaArray, 300); } /** * 对 View 做背景色变化的动作 * * @param v 做背景色变化的View * @param bgColor 背景色 * @param alphaArray 背景色变化的alpha数组,如 int[]{255,0} 表示从纯色变化到透明 * @param stepDuration 每一步变化的时长 * @param endAction 动画结束后的回调 */ @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) public static void playViewBackgroundAnimation(final View v, @ColorInt int bgColor, int[] alphaArray, int stepDuration, final Runnable endAction) { int animationCount = alphaArray.length - 1; Drawable bgDrawable = new ColorDrawable(bgColor); final Drawable oldBgDrawable = v.getBackground(); setBackgroundKeepingPadding(v, bgDrawable); List<Animator> animatorList = new ArrayList<Animator>(); for (int i = 0; i < animationCount; i++) { ObjectAnimator animator = ObjectAnimator.ofInt(v.getBackground(), "alpha", alphaArray[i], alphaArray[i + 1]); animatorList.add(animator); } AnimatorSet animatorSet = new AnimatorSet(); animatorSet.setDuration(stepDuration); animatorSet.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { setBackgroundKeepingPadding(v, oldBgDrawable); if (endAction != null) { endAction.run(); } } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); animatorSet.playSequentially(animatorList); animatorSet.start(); } @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) public static void playViewBackgroundAnimation(final View v, @ColorInt int bgColor, int[] alphaArray, int stepDuration) { playViewBackgroundAnimation(v, bgColor, alphaArray, stepDuration, null); } /** * 对 View 做背景色变化的动作 * * @param v 做背景色变化的View * @param startColor 动画开始时 View 的背景色 * @param endColor 动画结束时 View 的背景色 * @param duration 动画总时长 * @param repeatCount 动画重复次数 * @param setAnimTagId 将动画设置tag给view,若为0则不设置 * @param endAction 动画结束后的回调 */ @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) public static void playViewBackgroundAnimation(final View v, @ColorInt int startColor, @ColorInt int endColor, long duration, int repeatCount, int setAnimTagId, final Runnable endAction) { final Drawable oldBgDrawable = v.getBackground(); // 存储旧的背景 ViewHelper.setBackgroundColorKeepPadding(v, startColor); final ValueAnimator anim = new ValueAnimator(); anim.setIntValues(startColor, endColor); anim.setDuration(duration / (repeatCount + 1)); anim.setRepeatCount(repeatCount); anim.setRepeatMode(ValueAnimator.REVERSE); anim.setEvaluator(new ArgbEvaluator()); anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { ViewHelper.setBackgroundColorKeepPadding(v, (Integer) animation.getAnimatedValue()); } }); if (setAnimTagId != 0) { v.setTag(setAnimTagId, anim); } anim.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { setBackgroundKeepingPadding(v, oldBgDrawable); if (endAction != null) { endAction.run(); } } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); anim.start(); } @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) public static void playViewBackgroundAnimation(final View v, int startColor, int endColor, long duration) { playViewBackgroundAnimation(v, startColor, endColor, duration, 0, 0, null); } public static int generateViewId() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { return View.generateViewId(); } else { for (; ; ) { final int result = sNextGeneratedId.get(); // aapt-generated IDs have the high byte nonzero; clamp to the range under that. int newValue = result + 1; if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0. if (sNextGeneratedId.compareAndSet(result, newValue)) { return result; } } } } /** * <p>对 View 做透明度变化的进场动画。</p> * <p>相关方法 {@link #fadeOut(View, int, Animation.AnimationListener, boolean)}</p> * * @param view 做动画的 View * @param duration 动画时长(毫秒) * @param listener 动画回调 * @param isNeedAnimation 是否需要动画 */ @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) public static AlphaAnimation fadeIn(View view, int duration, Animation.AnimationListener listener, boolean isNeedAnimation) { if (view == null) { return null; } if (isNeedAnimation) { view.setVisibility(View.VISIBLE); AlphaAnimation alpha = new AlphaAnimation(0, 1); alpha.setInterpolator(new DecelerateInterpolator()); alpha.setDuration(duration); alpha.setFillAfter(true); if (listener != null) { alpha.setAnimationListener(listener); } view.startAnimation(alpha); return alpha; } else { view.setAlpha(1); view.setVisibility(View.VISIBLE); return null; } } /** * <p>对 View 做透明度变化的退场动画</p> * <p>相关方法 {@link #fadeIn(View, int, Animation.AnimationListener, boolean)}</p> * * @param view 做动画的 View * @param duration 动画时长(毫秒) * @param listener 动画回调 * @param isNeedAnimation 是否需要动画 */ public static AlphaAnimation fadeOut(final View view, int duration, final Animation.AnimationListener listener, boolean isNeedAnimation) { if (view == null) { return null; } if (isNeedAnimation) { AlphaAnimation alpha = new AlphaAnimation(1, 0); alpha.setInterpolator(new DecelerateInterpolator()); alpha.setDuration(duration); alpha.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { if (listener != null) { listener.onAnimationStart(animation); } } @Override public void onAnimationEnd(Animation animation) { view.setVisibility(View.GONE); if (listener != null) { listener.onAnimationEnd(animation); } } @Override public void onAnimationRepeat(Animation animation) { if (listener != null) { listener.onAnimationRepeat(animation); } } }); view.startAnimation(alpha); return alpha; } else { view.setVisibility(View.GONE); return null; } } @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) public static void clearValueAnimator(ValueAnimator animator) { if (animator != null) { animator.removeAllListeners(); animator.removeAllUpdateListeners(); if (Build.VERSION.SDK_INT >= 19) { animator.pause(); } animator.cancel(); } } public static Rect calcViewScreenLocation(View view) { int[] location = new int[2]; // 获取控件在屏幕中的位置,返回的数组分别为控件左顶点的 x、y 的值 view.getLocationOnScreen(location); return new Rect(location[0], location[1], location[0] + view.getWidth(), location[1] + view.getHeight()); } /** * <p>对 View 做上下位移的进场动画</p> * <p>相关方法 {@link #slideOut(View, int, Animation.AnimationListener, boolean, QMUIDirection)}</p> * * @param view 做动画的 View * @param duration 动画时长(毫秒) * @param listener 动画回调 * @param isNeedAnimation 是否需要动画 * @param direction 进场动画的方向 * @return 动画对应的 Animator 对象, 注意无动画时返回 null */ public static @Nullable TranslateAnimation slideIn(final View view, int duration, final Animation.AnimationListener listener, boolean isNeedAnimation, QMUIDirection direction) { if (view == null) { return null; } if (isNeedAnimation) { TranslateAnimation translate = null; switch (direction) { case LEFT_TO_RIGHT: translate = new TranslateAnimation( Animation.RELATIVE_TO_SELF, -1f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f ); break; case TOP_TO_BOTTOM: translate = new TranslateAnimation( Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, -1f, Animation.RELATIVE_TO_SELF, 0f ); break; case RIGHT_TO_LEFT: translate = new TranslateAnimation( Animation.RELATIVE_TO_SELF, 1f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f ); break; case BOTTOM_TO_TOP: translate = new TranslateAnimation( Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 1f, Animation.RELATIVE_TO_SELF, 0f ); break; } translate.setInterpolator(new DecelerateInterpolator()); translate.setDuration(duration); translate.setFillAfter(true); if (listener != null) { translate.setAnimationListener(listener); } view.setVisibility(View.VISIBLE); view.startAnimation(translate); return translate; } else { view.clearAnimation(); view.setVisibility(View.VISIBLE); return null; } } /** * <p>对 View 做上下位移的退场动画</p> * <p>相关方法 {@link #slideIn(View, int, Animation.AnimationListener, boolean, QMUIDirection)}</p> * * @param view 做动画的 View * @param duration 动画时长(毫秒) * @param listener 动画回调 * @param isNeedAnimation 是否需要动画 * @param direction 进场动画的方向 * @return 动画对应的 Animator 对象, 注意无动画时返回 null */ public static @Nullable TranslateAnimation slideOut(final View view, int duration, final Animation.AnimationListener listener, boolean isNeedAnimation, QMUIDirection direction) { if (view == null) { return null; } if (isNeedAnimation) { TranslateAnimation translate = null; switch (direction) { case LEFT_TO_RIGHT: translate = new TranslateAnimation( Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 1f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f ); break; case TOP_TO_BOTTOM: translate = new TranslateAnimation( Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 1f ); break; case RIGHT_TO_LEFT: translate = new TranslateAnimation( Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, -1f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f ); break; case BOTTOM_TO_TOP: translate = new TranslateAnimation( Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, -1f ); break; } translate.setInterpolator(new DecelerateInterpolator()); translate.setDuration(duration); translate.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { if (listener != null) { listener.onAnimationStart(animation); } } @Override public void onAnimationEnd(Animation animation) { view.setVisibility(View.GONE); if (listener != null) { listener.onAnimationEnd(animation); } } @Override public void onAnimationRepeat(Animation animation) { if (listener != null) { listener.onAnimationRepeat(animation); } } }); view.startAnimation(translate); return translate; } else { view.clearAnimation(); view.setVisibility(View.GONE); return null; } } /** * 对 View 设置 paddingLeft * * @param view 需要被设置的 View * @param value 设置的值 */ public static void setPaddingLeft(View view, int value) { view.setPadding(value, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); } /** * 对 View 设置 paddingTop * * @param view 需要被设置的 View * @param value 设置的值 */ public static void setPaddingTop(View view, int value) { view.setPadding(view.getPaddingLeft(), value, view.getPaddingRight(), view.getPaddingBottom()); } /** * 对 View 设置 paddingRight * * @param view 需要被设置的 View * @param value 设置的值 */ public static void setPaddingRight(View view, int value) { view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), value, view.getPaddingBottom()); } /** * 对 View 设置 paddingBottom * * @param view 需要被设置的 View * @param value 设置的值 */ public static void setPaddingBottom(View view, int value) { view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), value); } /** * 判断是否需要对 LineSpacingExtra 进行额外的兼容处理 * 安卓 5.0 以下版本中,LineSpacingExtra 在最后一行也会产生作用,因此会多出一个 LineSpacingExtra 的空白,可以通过该方法判断后进行兼容处理 * if (QMUIViewHelper.getISLastLineSpacingExtraError()) { * textView.bottomMargin = -3dp; * } else { * textView.bottomMargin = 0; * } */ public static boolean getIsLastLineSpacingExtraError() { return Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP; } /** * 把 ViewStub inflate 之后在其中根据 id 找 View * * @param parentView 包含 ViewStub 的 View * @param viewStubId 要从哪个 ViewStub 来 inflate * @param inflatedViewId 最终要找到的 View 的 id * @return id 为 inflatedViewId 的 View */ public static View findViewFromViewStub(View parentView, int viewStubId, int inflatedViewId) { if (null == parentView) { return null; } View view = parentView.findViewById(inflatedViewId); if (null == view) { ViewStub vs = (ViewStub) parentView.findViewById(viewStubId); if (null == vs) { return null; } view = vs.inflate(); if (null != view) { view = view.findViewById(inflatedViewId); } } return view; } /** * @param parentView * @param viewStubId * @param inflatedViewId * @param inflateLayoutResId * @return */ @SuppressLint("ResourceType") public static View findViewFromViewStub(View parentView, int viewStubId, int inflatedViewId, int inflateLayoutResId) { if (null == parentView) { return null; } View view = parentView.findViewById(inflatedViewId); if (null == view) { ViewStub vs = (ViewStub) parentView.findViewById(viewStubId); if (null == vs) { return null; } if (vs.getLayoutResource() < 1 && inflateLayoutResId > 0) { vs.setLayoutResource(inflateLayoutResId); } view = vs.inflate(); if (null != view) { view = view.findViewById(inflatedViewId); } } return view; } public static ColorFilter setImageViewTintColor(ImageView imageView, @ColorInt int tintColor) { LightingColorFilter colorFilter = new LightingColorFilter(Color.argb(255, 0, 0, 0), tintColor); imageView.setColorFilter(colorFilter); return colorFilter; } /** * 判断 ListView 是否已经滚动到底部 * * @param listView 需要被判断的 ListView * @return */ public static boolean isListViewAlreadyAtBottom(ListView listView) { if (listView.getAdapter() == null || listView.getHeight() == 0) { return false; } if (listView.getLastVisiblePosition() == listView.getAdapter().getCount() - 1) { View lastItemView = listView.getChildAt(listView.getChildCount() - 1); if (lastItemView != null && lastItemView.getBottom() == listView.getHeight()) { return true; } } return false; } /** * Retrieve the transformed bounding rect of an arbitrary descendant view. * This does not need to be a direct child. * * @param descendant descendant view to reference * @param out rect to set to the bounds of the descendant view */ @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) public static void getDescendantRect(ViewGroup parent, View descendant, Rect out) { out.set(0, 0, descendant.getWidth(), descendant.getHeight()); ViewGroupHelper.offsetDescendantRect(parent, descendant, out); } /** * Adjust TextView font size, adaptive width * * @param textView TextView * @param text Text * @param maxWidth Maximum width * @param dpMin Minimum dp limit, default dp * @param dpMax Maximum dp limit, default dp */ public static void autoSize(TextView textView, String text, float maxWidth, float dpMin, float dpMax) { final Paint paint = textView.getPaint(); final float minSize = DimenUtils.dp2px(textView.getContext(), dpMin); float textSize = DimenUtils.dp2px(textView.getContext(), dpMax); textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); // Get the effective width of the current TextView final int availableWidth = DimenUtils.dp2px(textView.getContext(), maxWidth); float textWidth = paint.measureText(text); while (textWidth > availableWidth) { if (textSize < minSize) { break; } textSize = textSize - 1; // The unit passed in here is px textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); textWidth = paint.measureText(text) + 2; } textView.setText(text); } private static class ViewGroupHelper { private static final ThreadLocal<Matrix> sMatrix = new ThreadLocal<>(); private static final ThreadLocal<RectF> sRectF = new ThreadLocal<>(); @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) public static void offsetDescendantRect(ViewGroup group, View child, Rect rect) { Matrix m = sMatrix.get(); if (m == null) { m = new Matrix(); sMatrix.set(m); } else { m.reset(); } offsetDescendantMatrix(group, child, m); RectF rectF = sRectF.get(); if (rectF == null) { rectF = new RectF(); sRectF.set(rectF); } rectF.set(rect); m.mapRect(rectF); rect.set((int) (rectF.left + 0.5f), (int) (rectF.top + 0.5f), (int) (rectF.right + 0.5f), (int) (rectF.bottom + 0.5f)); } @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) static void offsetDescendantMatrix(ViewParent target, View view, Matrix m) { final ViewParent parent = view.getParent(); if (parent instanceof View && parent != target) { final View vp = (View) parent; offsetDescendantMatrix(target, vp, m); m.preTranslate(-vp.getScrollX(), -vp.getScrollY()); } m.preTranslate(view.getLeft(), view.getTop()); if (!view.getMatrix().isIdentity()) { m.preConcat(view.getMatrix()); } } } /** * 定义了从左到右,从上到下,从右到左,从下到上四个方向的类 * Created by Kayo on 2017/2/7. */ public enum QMUIDirection { LEFT_TO_RIGHT, TOP_TO_BOTTOM, RIGHT_TO_LEFT, BOTTOM_TO_TOP } }