diff --git a/buildSrc/src/main/kotlin/com/quyunshuo/androidbaseframemvvm/buildsrc/DependencyConfig.kt b/buildSrc/src/main/kotlin/com/quyunshuo/androidbaseframemvvm/buildsrc/DependencyConfig.kt
index cb73a8a..42bb60a 100644
--- a/buildSrc/src/main/kotlin/com/quyunshuo/androidbaseframemvvm/buildsrc/DependencyConfig.kt
+++ b/buildSrc/src/main/kotlin/com/quyunshuo/androidbaseframemvvm/buildsrc/DependencyConfig.kt
@@ -50,7 +50,6 @@ object DependencyConfig {
const val ARoute = "1.5.1" // 阿里路由
const val ARouteCompiler = "1.5.1" // 阿里路由 APT
const val RecyclerViewAdapter = "3.0.4" // RecyclerViewAdapter
- const val StatusBar = "1.5.1" // 状态栏
const val EventBus = "3.2.0" // 事件总线
const val PermissionX = "1.4.0" // 权限申请
const val LeakCanary = "2.7" // 检测内存泄漏
@@ -146,7 +145,6 @@ object DependencyConfig {
const val ARouteCompiler = "com.alibaba:arouter-compiler:${Version.ARouteCompiler}"
const val RecyclerViewAdapter =
"com.github.CymChad:BaseRecyclerViewAdapterHelper:${Version.RecyclerViewAdapter}"
- const val StatusBar = "com.jaeger.statusbarutil:library:${Version.StatusBar}"
const val EventBus = "org.greenrobot:eventbus:${Version.EventBus}"
const val EventBusAPT = "org.greenrobot:eventbus-annotation-processor:${Version.EventBus}"
const val PermissionX = "com.permissionx.guolindev:permissionx:${Version.PermissionX}"
diff --git a/lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/BarUtils.java b/lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/BarUtils.java
new file mode 100644
index 0000000..693243e
--- /dev/null
+++ b/lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/BarUtils.java
@@ -0,0 +1,674 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Build;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RequiresPermission;
+
+import java.lang.reflect.Method;
+
+import static android.Manifest.permission.EXPAND_STATUS_BAR;
+
+import com.quyunshuo.androidbaseframemvvm.base.BaseApplication;
+
+/**
+ * 各种栏的工具类
+ *
+ * getStatusBarHeight : 获取状态栏高度(px)
+ * setStatusBarVisibility : 设置状态栏是否可见
+ * isStatusBarVisible : 判断状态栏是否可见
+ * setStatusBarLightMode : 设置状态栏是否为浅色模式
+ * isStatusBarLightMode : 判断状态栏是否为浅色模式
+ * addMarginTopEqualStatusBarHeight : 为 view 增加 MarginTop 为状态栏高度
+ * subtractMarginTopEqualStatusBarHeight: 为 view 减少 MarginTop 为状态栏高度
+ * setStatusBarColor : 设置状态栏颜色
+ * setStatusBarColor4Drawer : 为 DrawerLayout 设置状态栏颜色
+ * transparentStatusBar : 透明状态栏
+ * getActionBarHeight : 获取 ActionBar 高度
+ * setNotificationBarVisibility : 设置通知栏是否可见
+ * getNavBarHeight : 获取导航栏高度
+ * setNavBarVisibility : 设置导航栏是否可见
+ * isNavBarVisible : 判断导航栏是否可见
+ * setNavBarColor : 设置导航栏颜色
+ * getNavBarColor : 获取导航栏颜色
+ * isSupportNavBar : 判断是否支持导航栏
+ * setNavBarLightMode : 设置导航栏是否为浅色模式
+ * isNavBarLightMode : 判断导航栏是否为浅色模式
+ *
+ * @author Qu Yunshuo
+ * @since 2021/7/15 10:42 上午
+ */
+public final class BarUtils {
+
+ ///////////////////////////////////////////////////////////////////////////
+ // status bar
+ ///////////////////////////////////////////////////////////////////////////
+
+ private static final String TAG_STATUS_BAR = "TAG_STATUS_BAR";
+ private static final String TAG_OFFSET = "TAG_OFFSET";
+ private static final int KEY_OFFSET = -123;
+
+ private BarUtils() {
+ throw new UnsupportedOperationException("u can't instantiate me...");
+ }
+
+ /**
+ * Return the status bar's height.
+ *
+ * @return the status bar's height
+ */
+ public static int getStatusBarHeight() {
+ Resources resources = BaseApplication.context.getResources();
+ int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
+ return resources.getDimensionPixelSize(resourceId);
+ }
+
+ /**
+ * Set the status bar's visibility.
+ *
+ * @param activity The activity.
+ * @param isVisible True to set status bar visible, false otherwise.
+ */
+ public static void setStatusBarVisibility(@NonNull final Activity activity,
+ final boolean isVisible) {
+ setStatusBarVisibility(activity.getWindow(), isVisible);
+ }
+
+ /**
+ * Set the status bar's visibility.
+ *
+ * @param window The window.
+ * @param isVisible True to set status bar visible, false otherwise.
+ */
+ public static void setStatusBarVisibility(@NonNull final Window window,
+ final boolean isVisible) {
+ if (isVisible) {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ showStatusBarView(window);
+ addMarginTopEqualStatusBarHeight(window);
+ } else {
+ window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ hideStatusBarView(window);
+ subtractMarginTopEqualStatusBarHeight(window);
+ }
+ }
+
+ /**
+ * Return whether the status bar is visible.
+ *
+ * @param activity The activity.
+ * @return {@code true}: yes
{@code false}: no
+ */
+ public static boolean isStatusBarVisible(@NonNull final Activity activity) {
+ int flags = activity.getWindow().getAttributes().flags;
+ return (flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0;
+ }
+
+ /**
+ * Set the status bar's light mode.
+ *
+ * @param activity The activity.
+ * @param isLightMode True to set status bar light mode, false otherwise.
+ */
+ public static void setStatusBarLightMode(@NonNull final Activity activity,
+ final boolean isLightMode) {
+ setStatusBarLightMode(activity.getWindow(), isLightMode);
+ }
+
+ /**
+ * Set the status bar's light mode.
+ *
+ * @param window The window.
+ * @param isLightMode True to set status bar light mode, false otherwise.
+ */
+ public static void setStatusBarLightMode(@NonNull final Window window,
+ final boolean isLightMode) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ View decorView = window.getDecorView();
+ int vis = decorView.getSystemUiVisibility();
+ if (isLightMode) {
+ vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ } else {
+ vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ }
+ decorView.setSystemUiVisibility(vis);
+ }
+ }
+
+ /**
+ * Is the status bar light mode.
+ *
+ * @param activity The activity.
+ * @return {@code true}: yes
{@code false}: no
+ */
+ public static boolean isStatusBarLightMode(@NonNull final Activity activity) {
+ return isStatusBarLightMode(activity.getWindow());
+ }
+
+ /**
+ * Is the status bar light mode.
+ *
+ * @param window The window.
+ * @return {@code true}: yes
{@code false}: no
+ */
+ public static boolean isStatusBarLightMode(@NonNull final Window window) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ View decorView = window.getDecorView();
+ int vis = decorView.getSystemUiVisibility();
+ return (vis & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0;
+ }
+ return false;
+ }
+
+ /**
+ * Add the top margin size equals status bar's height for view.
+ *
+ * @param view The view.
+ */
+ public static void addMarginTopEqualStatusBarHeight(@NonNull View view) {
+
+ view.setTag(TAG_OFFSET);
+ Object haveSetOffset = view.getTag(KEY_OFFSET);
+ if (haveSetOffset != null && (Boolean) haveSetOffset) return;
+ MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
+ layoutParams.setMargins(layoutParams.leftMargin,
+ layoutParams.topMargin + getStatusBarHeight(),
+ layoutParams.rightMargin,
+ layoutParams.bottomMargin);
+ view.setTag(KEY_OFFSET, true);
+ }
+
+ /**
+ * Subtract the top margin size equals status bar's height for view.
+ *
+ * @param view The view.
+ */
+ public static void subtractMarginTopEqualStatusBarHeight(@NonNull View view) {
+
+ Object haveSetOffset = view.getTag(KEY_OFFSET);
+ if (haveSetOffset == null || !(Boolean) haveSetOffset) return;
+ MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
+ layoutParams.setMargins(layoutParams.leftMargin,
+ layoutParams.topMargin - getStatusBarHeight(),
+ layoutParams.rightMargin,
+ layoutParams.bottomMargin);
+ view.setTag(KEY_OFFSET, false);
+ }
+
+ private static void addMarginTopEqualStatusBarHeight(@NonNull final Window window) {
+ View withTag = window.getDecorView().findViewWithTag(TAG_OFFSET);
+ if (withTag == null) return;
+ addMarginTopEqualStatusBarHeight(withTag);
+ }
+
+ private static void subtractMarginTopEqualStatusBarHeight(@NonNull final Window window) {
+ View withTag = window.getDecorView().findViewWithTag(TAG_OFFSET);
+ if (withTag == null) return;
+ subtractMarginTopEqualStatusBarHeight(withTag);
+ }
+
+ /**
+ * Set the status bar's color.
+ *
+ * @param activity The activity.
+ * @param color The status bar's color.
+ */
+ public static View setStatusBarColor(@NonNull final Activity activity,
+ @ColorInt final int color) {
+ return setStatusBarColor(activity, color, false);
+ }
+
+ /**
+ * Set the status bar's color.
+ *
+ * @param activity The activity.
+ * @param color The status bar's color.
+ * @param isDecor True to add fake status bar in DecorView,
+ * false to add fake status bar in ContentView.
+ */
+ public static View setStatusBarColor(@NonNull final Activity activity,
+ @ColorInt final int color,
+ final boolean isDecor) {
+ transparentStatusBar(activity);
+ return applyStatusBarColor(activity, color, isDecor);
+ }
+
+
+ /**
+ * Set the status bar's color.
+ *
+ * @param window The window.
+ * @param color The status bar's color.
+ */
+ public static View setStatusBarColor(@NonNull final Window window,
+ @ColorInt final int color) {
+ return setStatusBarColor(window, color, false);
+ }
+
+ /**
+ * Set the status bar's color.
+ *
+ * @param window The window.
+ * @param color The status bar's color.
+ * @param isDecor True to add fake status bar in DecorView,
+ * false to add fake status bar in ContentView.
+ */
+ public static View setStatusBarColor(@NonNull final Window window,
+ @ColorInt final int color,
+ final boolean isDecor) {
+ transparentStatusBar(window);
+ return applyStatusBarColor(window, color, isDecor);
+ }
+
+ /**
+ * Set the status bar's color.
+ *
+ * @param fakeStatusBar The fake status bar view.
+ * @param color The status bar's color.
+ */
+ public static void setStatusBarColor(Activity activity, @NonNull final View fakeStatusBar,
+ @ColorInt final int color) {
+ if (activity == null) return;
+ transparentStatusBar(activity);
+ fakeStatusBar.setVisibility(View.VISIBLE);
+ ViewGroup.LayoutParams layoutParams = fakeStatusBar.getLayoutParams();
+ layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ layoutParams.height = getStatusBarHeight();
+ fakeStatusBar.setBackgroundColor(color);
+ }
+
+ /**
+ * Set the custom status bar.
+ *
+ * @param fakeStatusBar The fake status bar view.
+ */
+ public static void setStatusBarCustom(Activity activity, @NonNull final View fakeStatusBar) {
+ if (activity == null) return;
+ transparentStatusBar(activity);
+ fakeStatusBar.setVisibility(View.VISIBLE);
+ ViewGroup.LayoutParams layoutParams = fakeStatusBar.getLayoutParams();
+ if (layoutParams == null) {
+ layoutParams = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ getStatusBarHeight()
+ );
+ fakeStatusBar.setLayoutParams(layoutParams);
+ } else {
+ layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ layoutParams.height = getStatusBarHeight();
+ }
+ }
+
+ private static View applyStatusBarColor(@NonNull final Activity activity,
+ final int color,
+ boolean isDecor) {
+ return applyStatusBarColor(activity.getWindow(), color, isDecor);
+ }
+
+ private static View applyStatusBarColor(@NonNull final Window window,
+ final int color,
+ boolean isDecor) {
+ ViewGroup parent = isDecor ?
+ (ViewGroup) window.getDecorView() :
+ (ViewGroup) window.findViewById(android.R.id.content);
+ View fakeStatusBarView = parent.findViewWithTag(TAG_STATUS_BAR);
+ if (fakeStatusBarView != null) {
+ if (fakeStatusBarView.getVisibility() == View.GONE) {
+ fakeStatusBarView.setVisibility(View.VISIBLE);
+ }
+ fakeStatusBarView.setBackgroundColor(color);
+ } else {
+ fakeStatusBarView = createStatusBarView(window.getContext(), color);
+ parent.addView(fakeStatusBarView);
+ }
+ return fakeStatusBarView;
+ }
+
+ private static void hideStatusBarView(@NonNull final Activity activity) {
+ hideStatusBarView(activity.getWindow());
+ }
+
+ private static void hideStatusBarView(@NonNull final Window window) {
+ ViewGroup decorView = (ViewGroup) window.getDecorView();
+ View fakeStatusBarView = decorView.findViewWithTag(TAG_STATUS_BAR);
+ if (fakeStatusBarView == null) return;
+ fakeStatusBarView.setVisibility(View.GONE);
+ }
+
+ private static void showStatusBarView(@NonNull final Window window) {
+ ViewGroup decorView = (ViewGroup) window.getDecorView();
+ View fakeStatusBarView = decorView.findViewWithTag(TAG_STATUS_BAR);
+ if (fakeStatusBarView == null) return;
+ fakeStatusBarView.setVisibility(View.VISIBLE);
+ }
+
+ private static View createStatusBarView(@NonNull final Context context,
+ final int color) {
+ View statusBarView = new View(context);
+ statusBarView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight()));
+ statusBarView.setBackgroundColor(color);
+ statusBarView.setTag(TAG_STATUS_BAR);
+ return statusBarView;
+ }
+
+ public static void transparentStatusBar(@NonNull final Activity activity) {
+ transparentStatusBar(activity.getWindow());
+ }
+
+ public static void transparentStatusBar(@NonNull final Window window) {
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ int option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ int vis = window.getDecorView().getSystemUiVisibility();
+ window.getDecorView().setSystemUiVisibility(option | vis);
+ window.setStatusBarColor(Color.TRANSPARENT);
+ } else {
+ window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // action bar
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Return the action bar's height.
+ *
+ * @return the action bar's height
+ */
+ public static int getActionBarHeight() {
+ TypedValue tv = new TypedValue();
+ if (BaseApplication.context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
+ return TypedValue.complexToDimensionPixelSize(
+ tv.data, BaseApplication.context.getResources().getDisplayMetrics()
+ );
+ }
+ return 0;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // notification bar
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Set the notification bar's visibility.
+ *
Must hold {@code }
+ *
+ * @param isVisible True to set notification bar visible, false otherwise.
+ */
+ @RequiresPermission(EXPAND_STATUS_BAR)
+ public static void setNotificationBarVisibility(final boolean isVisible) {
+ String methodName;
+ if (isVisible) {
+ methodName = (Build.VERSION.SDK_INT <= 16) ? "expand" : "expandNotificationsPanel";
+ } else {
+ methodName = (Build.VERSION.SDK_INT <= 16) ? "collapse" : "collapsePanels";
+ }
+ invokePanels(methodName);
+ }
+
+ private static void invokePanels(final String methodName) {
+ try {
+ @SuppressLint("WrongConstant")
+ Object service = BaseApplication.context.getSystemService("statusbar");
+ @SuppressLint("PrivateApi")
+ Class> statusBarManager = Class.forName("android.app.StatusBarManager");
+ Method expand = statusBarManager.getMethod(methodName);
+ expand.invoke(service);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // navigation bar
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Return the navigation bar's height.
+ *
+ * @return the navigation bar's height
+ */
+ public static int getNavBarHeight() {
+ Resources res = BaseApplication.context.getResources();
+ int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");
+ if (resourceId != 0) {
+ return res.getDimensionPixelSize(resourceId);
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Set the navigation bar's visibility.
+ *
+ * @param activity The activity.
+ * @param isVisible True to set navigation bar visible, false otherwise.
+ */
+ public static void setNavBarVisibility(@NonNull final Activity activity, boolean isVisible) {
+
+ setNavBarVisibility(activity.getWindow(), isVisible);
+
+ }
+
+ /**
+ * Set the navigation bar's visibility.
+ *
+ * @param window The window.
+ * @param isVisible True to set navigation bar visible, false otherwise.
+ */
+ public static void setNavBarVisibility(@NonNull final Window window, boolean isVisible) {
+
+ final ViewGroup decorView = (ViewGroup) window.getDecorView();
+ for (int i = 0, count = decorView.getChildCount(); i < count; i++) {
+ final View child = decorView.getChildAt(i);
+ final int id = child.getId();
+ if (id != View.NO_ID) {
+ String resourceEntryName = getResNameById(id);
+ if ("navigationBarBackground".equals(resourceEntryName)) {
+ child.setVisibility(isVisible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+ }
+ final int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+ if (isVisible) {
+ decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() & ~uiOptions);
+ } else {
+ decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | uiOptions);
+ }
+ }
+
+ /**
+ * Return whether the navigation bar visible.
+ * Call it in onWindowFocusChanged will get right result.
+ *
+ * @param activity The activity.
+ * @return {@code true}: yes
{@code false}: no
+ */
+ public static boolean isNavBarVisible(@NonNull final Activity activity) {
+ return isNavBarVisible(activity.getWindow());
+ }
+
+ /**
+ * Return whether the navigation bar visible.
+ * Call it in onWindowFocusChanged will get right result.
+ *
+ * @param window The window.
+ * @return {@code true}: yes
{@code false}: no
+ */
+ public static boolean isNavBarVisible(@NonNull final Window window) {
+ boolean isVisible = false;
+ ViewGroup decorView = (ViewGroup) window.getDecorView();
+ for (int i = 0, count = decorView.getChildCount(); i < count; i++) {
+ final View child = decorView.getChildAt(i);
+ final int id = child.getId();
+ if (id != View.NO_ID) {
+ String resourceEntryName = getResNameById(id);
+ if ("navigationBarBackground".equals(resourceEntryName)
+ && child.getVisibility() == View.VISIBLE) {
+ isVisible = true;
+ break;
+ }
+ }
+ }
+ if (isVisible) {
+ int visibility = decorView.getSystemUiVisibility();
+ isVisible = (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
+ }
+
+ return isVisible;
+ }
+
+ private static String getResNameById(int id) {
+ try {
+ return BaseApplication.context.getResources().getResourceEntryName(id);
+ } catch (Exception ignore) {
+ return "";
+ }
+ }
+
+ /**
+ * Set the navigation bar's color.
+ *
+ * @param activity The activity.
+ * @param color The navigation bar's color.
+ */
+ @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+ public static void setNavBarColor(@NonNull final Activity activity, @ColorInt final int color) {
+ setNavBarColor(activity.getWindow(), color);
+ }
+
+ /**
+ * Set the navigation bar's color.
+ *
+ * @param window The window.
+ * @param color The navigation bar's color.
+ */
+ @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+ public static void setNavBarColor(@NonNull final Window window, @ColorInt final int color) {
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ window.setNavigationBarColor(color);
+ }
+
+ /**
+ * Return the color of navigation bar.
+ *
+ * @param activity The activity.
+ * @return the color of navigation bar
+ */
+ @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+ public static int getNavBarColor(@NonNull final Activity activity) {
+ return getNavBarColor(activity.getWindow());
+ }
+
+ /**
+ * Return the color of navigation bar.
+ *
+ * @param window The window.
+ * @return the color of navigation bar
+ */
+ @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+ public static int getNavBarColor(@NonNull final Window window) {
+ return window.getNavigationBarColor();
+ }
+
+ /**
+ * Return whether the navigation bar visible.
+ *
+ * @return {@code true}: yes
{@code false}: no
+ */
+ public static boolean isSupportNavBar() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ WindowManager wm = (WindowManager) BaseApplication.context.getSystemService(Context.WINDOW_SERVICE);
+ if (wm == null) return false;
+ Display display = wm.getDefaultDisplay();
+ Point size = new Point();
+ Point realSize = new Point();
+ display.getSize(size);
+ display.getRealSize(realSize);
+ return realSize.y != size.y || realSize.x != size.x;
+ }
+ boolean menu = ViewConfiguration.get(BaseApplication.context).hasPermanentMenuKey();
+ boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
+ return !menu && !back;
+ }
+
+ /**
+ * Set the nav bar's light mode.
+ *
+ * @param activity The activity.
+ * @param isLightMode True to set nav bar light mode, false otherwise.
+ */
+ public static void setNavBarLightMode(@NonNull final Activity activity,
+ final boolean isLightMode) {
+ setNavBarLightMode(activity.getWindow(), isLightMode);
+ }
+
+ /**
+ * Set the nav bar's light mode.
+ *
+ * @param window The window.
+ * @param isLightMode True to set nav bar light mode, false otherwise.
+ */
+ public static void setNavBarLightMode(@NonNull final Window window,
+ final boolean isLightMode) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ View decorView = window.getDecorView();
+ int vis = decorView.getSystemUiVisibility();
+ if (isLightMode) {
+ vis |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+ } else {
+ vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+ }
+ decorView.setSystemUiVisibility(vis);
+ }
+ }
+
+ /**
+ * Is the nav bar light mode.
+ *
+ * @param activity The activity.
+ * @return {@code true}: yes
{@code false}: no
+ */
+ public static boolean isNavBarLightMode(@NonNull final Activity activity) {
+ return isNavBarLightMode(activity.getWindow());
+ }
+
+ /**
+ * Is the nav bar light mode.
+ *
+ * @param window The window.
+ * @return {@code true}: yes
{@code false}: no
+ */
+ public static boolean isNavBarLightMode(@NonNull final Window window) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ View decorView = window.getDecorView();
+ int vis = decorView.getSystemUiVisibility();
+ return (vis & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/CoilGIFImageLoader.kt b/lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/CoilGIFImageLoader.kt
new file mode 100644
index 0000000..9b80c34
--- /dev/null
+++ b/lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/CoilGIFImageLoader.kt
@@ -0,0 +1,26 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils
+
+import android.os.Build.VERSION.SDK_INT
+import coil.ImageLoader
+import coil.decode.GifDecoder
+import coil.decode.ImageDecoderDecoder
+import com.quyunshuo.androidbaseframemvvm.base.BaseApplication
+
+/**
+ * 用于加载 Gif 的 Coil ImageLoader
+ *
+ * @author Qu Yunshuo
+ * @since 2021/9/6 4:26 下午
+ */
+object CoilGIFImageLoader {
+
+ val imageLoader = ImageLoader.Builder(BaseApplication.context)
+ .componentRegistry {
+ if (SDK_INT >= 28) {
+ add(ImageDecoderDecoder(BaseApplication.context))
+ } else {
+ add(GifDecoder())
+ }
+ }
+ .build()
+}
\ No newline at end of file
diff --git a/lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/SpannableStringUtils.java b/lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/SpannableStringUtils.java
new file mode 100644
index 0000000..136b792
--- /dev/null
+++ b/lib_base/src/main/java/com/quyunshuo/androidbaseframemvvm/base/utils/SpannableStringUtils.java
@@ -0,0 +1,1497 @@
+package com.quyunshuo.androidbaseframemvvm.base.utils;
+
+import android.annotation.SuppressLint;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.text.Layout;
+import android.text.Layout.Alignment;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.method.LinkMovementMethod;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.AlignmentSpan;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.CharacterStyle;
+import android.text.style.ClickableSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.LeadingMarginSpan;
+import android.text.style.LineHeightSpan;
+import android.text.style.MaskFilterSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.ReplacementSpan;
+import android.text.style.ScaleXSpan;
+import android.text.style.StrikethroughSpan;
+import android.text.style.StyleSpan;
+import android.text.style.SubscriptSpan;
+import android.text.style.SuperscriptSpan;
+import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
+import android.text.style.UnderlineSpan;
+import android.text.style.UpdateAppearance;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.FloatRange;
+import androidx.annotation.IntDef;
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import java.io.InputStream;
+import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+
+import static android.graphics.BlurMaskFilter.Blur;
+
+import com.quyunshuo.androidbaseframemvvm.base.BaseApplication;
+
+/**
+ * 字符串相关
+ * 相关方法:
+ *
+ * with() : 设置控件
+ * setFlag() : 设置标识
+ * setForegroundColor(): 设置前景色
+ * setBackgroundColor(): 设置背景色
+ * setLineHeight() : 设置行高
+ * setQuoteColor() : 设置引用线的颜色
+ * setLeadingMargin() : 设置缩进
+ * setBullet() : 设置列表标记
+ * setFontSize() : 设置字体尺寸
+ * setFontProportion() : 设置字体比例
+ * setFontXProportion(): 设置字体横向比例
+ * setStrikethrough() : 设置删除线
+ * setUnderline() : 设置下划线
+ * setSuperscript() : 设置上标
+ * setSubscript() : 设置下标
+ * setBold() : 设置粗体
+ * setItalic() : 设置斜体
+ * setBoldItalic() : 设置粗斜体
+ * setFontFamily() : 设置字体系列
+ * setTypeface() : 设置字体
+ * setAlign() : 设置对齐
+ * setClickSpan() : 设置点击事件
+ * setUrl() : 设置超链接
+ * setBlur() : 设置模糊
+ * setShader() : 设置着色器
+ * setShadow() : 设置阴影
+ * setSpans() : 设置样式
+ * append() : 追加样式字符串
+ * appendLine() : 追加一行样式字符串
+ * appendImage() : 追加图片
+ * appendSpace() : 追加空白
+ * create() : 创建样式字符串
+ *
+ * 示例:
+ *
+ * SpanUtils.with(TextView)
+ * .append("String")
+ * .append("String")
+ * .setForegroundColor(ContextCompat.getColor(getContext(), R.color.color666666))
+ * .setFontSize(14, true)
+ * .create();
+ *
+ *
+ * @author Qu Yunshuo
+ * @since 2020/9/17
+ */
+public final class SpannableStringUtils {
+
+ private static final int COLOR_DEFAULT = 0xFEFFFFFF;
+
+ public static final int ALIGN_BOTTOM = 0;
+ public static final int ALIGN_BASELINE = 1;
+ public static final int ALIGN_CENTER = 2;
+ public static final int ALIGN_TOP = 3;
+
+ @IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER, ALIGN_TOP})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Align {
+ }
+
+ private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+ public static SpannableStringUtils with(final TextView textView) {
+ return new SpannableStringUtils(textView);
+ }
+
+ private TextView mTextView;
+ private CharSequence mText;
+ private int flag;
+ private int foregroundColor;
+ private int backgroundColor;
+ private int lineHeight;
+ private int alignLine;
+ private int quoteColor;
+ private int stripeWidth;
+ private int quoteGapWidth;
+ private int first;
+ private int rest;
+ private int bulletColor;
+ private int bulletRadius;
+ private int bulletGapWidth;
+ private int fontSize;
+ private boolean fontSizeIsDp;
+ private float proportion;
+ private float xProportion;
+ private boolean isStrikethrough;
+ private boolean isUnderline;
+ private boolean isSuperscript;
+ private boolean isSubscript;
+ private boolean isBold;
+ private boolean isItalic;
+ private boolean isBoldItalic;
+ private String fontFamily;
+ private Typeface typeface;
+ private Alignment alignment;
+ private int verticalAlign;
+ private ClickableSpan clickSpan;
+ private String url;
+ private float blurRadius;
+ private Blur style;
+ private Shader shader;
+ private float shadowRadius;
+ private float shadowDx;
+ private float shadowDy;
+ private int shadowColor;
+ private Object[] spans;
+
+ private Bitmap imageBitmap;
+ private Drawable imageDrawable;
+ private Uri imageUri;
+ private int imageResourceId;
+ private int alignImage;
+
+ private int spaceSize;
+ private int spaceColor;
+
+ private SerializableSpannableStringBuilder mBuilder;
+ private boolean isCreated;
+
+ private int mType;
+ private final int mTypeCharSequence = 0;
+ private final int mTypeImage = 1;
+ private final int mTypeSpace = 2;
+
+ private SpannableStringUtils(TextView textView) {
+ this();
+ mTextView = textView;
+ }
+
+ public SpannableStringUtils() {
+ mBuilder = new SerializableSpannableStringBuilder();
+ mText = "";
+ mType = -1;
+ setDefault();
+ }
+
+ private void setDefault() {
+ flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
+ foregroundColor = COLOR_DEFAULT;
+ backgroundColor = COLOR_DEFAULT;
+ lineHeight = -1;
+ quoteColor = COLOR_DEFAULT;
+ first = -1;
+ bulletColor = COLOR_DEFAULT;
+ fontSize = -1;
+ proportion = -1;
+ xProportion = -1;
+ isStrikethrough = false;
+ isUnderline = false;
+ isSuperscript = false;
+ isSubscript = false;
+ isBold = false;
+ isItalic = false;
+ isBoldItalic = false;
+ fontFamily = null;
+ typeface = null;
+ alignment = null;
+ verticalAlign = -1;
+ clickSpan = null;
+ url = null;
+ blurRadius = -1;
+ shader = null;
+ shadowRadius = -1;
+ spans = null;
+
+ imageBitmap = null;
+ imageDrawable = null;
+ imageUri = null;
+ imageResourceId = -1;
+
+ spaceSize = -1;
+ }
+
+ /**
+ * Set the span of flag.
+ *
+ * @param flag The flag.
+ *
+ * - {@link Spanned#SPAN_INCLUSIVE_EXCLUSIVE}
+ * - {@link Spanned#SPAN_INCLUSIVE_INCLUSIVE}
+ * - {@link Spanned#SPAN_EXCLUSIVE_EXCLUSIVE}
+ * - {@link Spanned#SPAN_EXCLUSIVE_INCLUSIVE}
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setFlag(final int flag) {
+ this.flag = flag;
+ return this;
+ }
+
+ /**
+ * Set the span of foreground's color.
+ *
+ * @param color The color of foreground
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setForegroundColor(@ColorInt final int color) {
+ this.foregroundColor = color;
+ return this;
+ }
+
+ /**
+ * Set the span of background's color.
+ *
+ * @param color The color of background
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setBackgroundColor(@ColorInt final int color) {
+ this.backgroundColor = color;
+ return this;
+ }
+
+ /**
+ * Set the span of line height.
+ *
+ * @param lineHeight The line height, in pixel.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setLineHeight(@IntRange(from = 0) final int lineHeight) {
+ return setLineHeight(lineHeight, ALIGN_CENTER);
+ }
+
+ /**
+ * Set the span of line height.
+ *
+ * @param lineHeight The line height, in pixel.
+ * @param align The alignment.
+ *
+ * - {@link Align#ALIGN_TOP }
+ * - {@link Align#ALIGN_CENTER}
+ * - {@link Align#ALIGN_BOTTOM}
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setLineHeight(@IntRange(from = 0) final int lineHeight,
+ @Align final int align) {
+ this.lineHeight = lineHeight;
+ this.alignLine = align;
+ return this;
+ }
+
+ /**
+ * Set the span of quote's color.
+ *
+ * @param color The color of quote
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setQuoteColor(@ColorInt final int color) {
+ return setQuoteColor(color, 2, 2);
+ }
+
+ /**
+ * Set the span of quote's color.
+ *
+ * @param color The color of quote.
+ * @param stripeWidth The width of stripe, in pixel.
+ * @param gapWidth The width of gap, in pixel.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setQuoteColor(@ColorInt final int color,
+ @IntRange(from = 1) final int stripeWidth,
+ @IntRange(from = 0) final int gapWidth) {
+ this.quoteColor = color;
+ this.stripeWidth = stripeWidth;
+ this.quoteGapWidth = gapWidth;
+ return this;
+ }
+
+ /**
+ * Set the span of leading margin.
+ *
+ * @param first The indent for the first line of the paragraph.
+ * @param rest The indent for the remaining lines of the paragraph.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setLeadingMargin(@IntRange(from = 0) final int first,
+ @IntRange(from = 0) final int rest) {
+ this.first = first;
+ this.rest = rest;
+ return this;
+ }
+
+ /**
+ * Set the span of bullet.
+ *
+ * @param gapWidth The width of gap, in pixel.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setBullet(@IntRange(from = 0) final int gapWidth) {
+ return setBullet(0, 3, gapWidth);
+ }
+
+ /**
+ * Set the span of bullet.
+ *
+ * @param color The color of bullet.
+ * @param radius The radius of bullet, in pixel.
+ * @param gapWidth The width of gap, in pixel.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setBullet(@ColorInt final int color,
+ @IntRange(from = 0) final int radius,
+ @IntRange(from = 0) final int gapWidth) {
+ this.bulletColor = color;
+ this.bulletRadius = radius;
+ this.bulletGapWidth = gapWidth;
+ return this;
+ }
+
+ /**
+ * Set the span of font's size.
+ *
+ * @param size The size of font.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setFontSize(@IntRange(from = 0) final int size) {
+ return setFontSize(size, false);
+ }
+
+ /**
+ * Set the span of size of font.
+ *
+ * @param size The size of font.
+ * @param isSp True to use sp, false to use pixel.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setFontSize(@IntRange(from = 0) final int size, final boolean isSp) {
+ this.fontSize = size;
+ this.fontSizeIsDp = isSp;
+ return this;
+ }
+
+ /**
+ * Set the span of proportion of font.
+ *
+ * @param proportion The proportion of font.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setFontProportion(final float proportion) {
+ this.proportion = proportion;
+ return this;
+ }
+
+ /**
+ * Set the span of transverse proportion of font.
+ *
+ * @param proportion The transverse proportion of font.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setFontXProportion(final float proportion) {
+ this.xProportion = proportion;
+ return this;
+ }
+
+ /**
+ * Set the span of strikethrough.
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setStrikethrough() {
+ this.isStrikethrough = true;
+ return this;
+ }
+
+ /**
+ * Set the span of underline.
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setUnderline() {
+ this.isUnderline = true;
+ return this;
+ }
+
+ /**
+ * Set the span of superscript.
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setSuperscript() {
+ this.isSuperscript = true;
+ return this;
+ }
+
+ /**
+ * Set the span of subscript.
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setSubscript() {
+ this.isSubscript = true;
+ return this;
+ }
+
+ /**
+ * Set the span of bold.
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setBold() {
+ isBold = true;
+ return this;
+ }
+
+ /**
+ * Set the span of italic.
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setItalic() {
+ isItalic = true;
+ return this;
+ }
+
+ /**
+ * Set the span of bold italic.
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setBoldItalic() {
+ isBoldItalic = true;
+ return this;
+ }
+
+ /**
+ * Set the span of font family.
+ *
+ * @param fontFamily The font family.
+ *
+ * - monospace
+ * - serif
+ * - sans-serif
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setFontFamily(@NonNull final String fontFamily) {
+ this.fontFamily = fontFamily;
+ return this;
+ }
+
+ /**
+ * Set the span of typeface.
+ *
+ * @param typeface The typeface.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setTypeface(@NonNull final Typeface typeface) {
+ this.typeface = typeface;
+ return this;
+ }
+
+ /**
+ * Set the span of horizontal alignment.
+ *
+ * @param alignment The alignment.
+ *
+ * - {@link Alignment#ALIGN_NORMAL }
+ * - {@link Alignment#ALIGN_OPPOSITE}
+ * - {@link Alignment#ALIGN_CENTER }
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setHorizontalAlign(@NonNull final Alignment alignment) {
+ this.alignment = alignment;
+ return this;
+ }
+
+ /**
+ * Set the span of vertical alignment.
+ *
+ * @param align The alignment.
+ *
+ * - {@link Align#ALIGN_TOP }
+ * - {@link Align#ALIGN_CENTER }
+ * - {@link Align#ALIGN_BASELINE}
+ * - {@link Align#ALIGN_BOTTOM }
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setVerticalAlign(@Align final int align) {
+ this.verticalAlign = align;
+ return this;
+ }
+
+ /**
+ * Set the span of click.
+ * Must set {@code view.setMovementMethod(LinkMovementMethod.getInstance())}
+ *
+ * @param clickSpan The span of click.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setClickSpan(@NonNull final ClickableSpan clickSpan) {
+ if (mTextView != null && mTextView.getMovementMethod() == null) {
+ mTextView.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+ this.clickSpan = clickSpan;
+ return this;
+ }
+
+ /**
+ * Set the span of click.
+ * Must set {@code view.setMovementMethod(LinkMovementMethod.getInstance())}
+ *
+ * @param color The color of click span.
+ * @param underlineText True to support underline, false otherwise.
+ * @param listener The listener of click span.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setClickSpan(@ColorInt final int color,
+ final boolean underlineText,
+ final View.OnClickListener listener) {
+ if (mTextView != null && mTextView.getMovementMethod() == null) {
+ mTextView.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+ this.clickSpan = new ClickableSpan() {
+
+ @Override
+ public void updateDrawState(@NonNull TextPaint paint) {
+ paint.setColor(color);
+ paint.setUnderlineText(underlineText);
+ }
+
+ @Override
+ public void onClick(@NonNull View widget) {
+ if (listener != null) {
+ listener.onClick(widget);
+ }
+ }
+ };
+ return this;
+ }
+
+ /**
+ * Set the span of url.
+ * Must set {@code view.setMovementMethod(LinkMovementMethod.getInstance())}
+ *
+ * @param url The url.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setUrl(@NonNull final String url) {
+ if (mTextView != null && mTextView.getMovementMethod() == null) {
+ mTextView.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+ this.url = url;
+ return this;
+ }
+
+ /**
+ * Set the span of blur.
+ *
+ * @param radius The radius of blur.
+ * @param style The style.
+ *
+ * - {@link Blur#NORMAL}
+ * - {@link Blur#SOLID}
+ * - {@link Blur#OUTER}
+ * - {@link Blur#INNER}
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setBlur(@FloatRange(from = 0, fromInclusive = false) final float radius,
+ final Blur style) {
+ this.blurRadius = radius;
+ this.style = style;
+ return this;
+ }
+
+ /**
+ * Set the span of shader.
+ *
+ * @param shader The shader.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setShader(@NonNull final Shader shader) {
+ this.shader = shader;
+ return this;
+ }
+
+ /**
+ * Set the span of shadow.
+ *
+ * @param radius The radius of shadow.
+ * @param dx X-axis offset, in pixel.
+ * @param dy Y-axis offset, in pixel.
+ * @param shadowColor The color of shadow.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setShadow(@FloatRange(from = 0, fromInclusive = false) final float radius,
+ final float dx,
+ final float dy,
+ final int shadowColor) {
+ this.shadowRadius = radius;
+ this.shadowDx = dx;
+ this.shadowDy = dy;
+ this.shadowColor = shadowColor;
+ return this;
+ }
+
+
+ /**
+ * Set the spans.
+ *
+ * @param spans The spans.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils setSpans(@NonNull final Object... spans) {
+ if (spans.length > 0) {
+ this.spans = spans;
+ }
+ return this;
+ }
+
+ /**
+ * Append the text text.
+ *
+ * @param text The text.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils append(@NonNull final CharSequence text) {
+ apply(mTypeCharSequence);
+ mText = text;
+ return this;
+ }
+
+ /**
+ * Append one line.
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils appendLine() {
+ apply(mTypeCharSequence);
+ mText = LINE_SEPARATOR;
+ return this;
+ }
+
+ /**
+ * Append text and one line.
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils appendLine(@NonNull final CharSequence text) {
+ apply(mTypeCharSequence);
+ mText = text + LINE_SEPARATOR;
+ return this;
+ }
+
+ /**
+ * Append one image.
+ *
+ * @param bitmap The bitmap of image.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils appendImage(@NonNull final Bitmap bitmap) {
+ return appendImage(bitmap, ALIGN_BOTTOM);
+ }
+
+ /**
+ * Append one image.
+ *
+ * @param bitmap The bitmap.
+ * @param align The alignment.
+ *
+ * - {@link Align#ALIGN_TOP }
+ * - {@link Align#ALIGN_CENTER }
+ * - {@link Align#ALIGN_BASELINE}
+ * - {@link Align#ALIGN_BOTTOM }
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils appendImage(@NonNull final Bitmap bitmap, @Align final int align) {
+ apply(mTypeImage);
+ this.imageBitmap = bitmap;
+ this.alignImage = align;
+ return this;
+ }
+
+ /**
+ * Append one image.
+ *
+ * @param drawable The drawable of image.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils appendImage(@NonNull final Drawable drawable) {
+ return appendImage(drawable, ALIGN_BOTTOM);
+ }
+
+ /**
+ * Append one image.
+ *
+ * @param drawable The drawable of image.
+ * @param align The alignment.
+ *
+ * - {@link Align#ALIGN_TOP }
+ * - {@link Align#ALIGN_CENTER }
+ * - {@link Align#ALIGN_BASELINE}
+ * - {@link Align#ALIGN_BOTTOM }
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils appendImage(@NonNull final Drawable drawable, @Align final int align) {
+ apply(mTypeImage);
+ this.imageDrawable = drawable;
+ this.alignImage = align;
+ return this;
+ }
+
+ /**
+ * Append one image.
+ *
+ * @param uri The uri of image.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils appendImage(@NonNull final Uri uri) {
+ return appendImage(uri, ALIGN_BOTTOM);
+ }
+
+ /**
+ * Append one image.
+ *
+ * @param uri The uri of image.
+ * @param align The alignment.
+ *
+ * - {@link Align#ALIGN_TOP }
+ * - {@link Align#ALIGN_CENTER }
+ * - {@link Align#ALIGN_BASELINE}
+ * - {@link Align#ALIGN_BOTTOM }
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils appendImage(@NonNull final Uri uri, @Align final int align) {
+ apply(mTypeImage);
+ this.imageUri = uri;
+ this.alignImage = align;
+ return this;
+ }
+
+ /**
+ * Append one image.
+ *
+ * @param resourceId The resource id of image.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils appendImage(@DrawableRes final int resourceId) {
+ return appendImage(resourceId, ALIGN_BOTTOM);
+ }
+
+ /**
+ * Append one image.
+ *
+ * @param resourceId The resource id of image.
+ * @param align The alignment.
+ *
+ * - {@link Align#ALIGN_TOP }
+ * - {@link Align#ALIGN_CENTER }
+ * - {@link Align#ALIGN_BASELINE}
+ * - {@link Align#ALIGN_BOTTOM }
+ *
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils appendImage(@DrawableRes final int resourceId, @Align final int align) {
+ apply(mTypeImage);
+ this.imageResourceId = resourceId;
+ this.alignImage = align;
+ return this;
+ }
+
+ /**
+ * Append space.
+ *
+ * @param size The size of space.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils appendSpace(@IntRange(from = 0) final int size) {
+ return appendSpace(size, Color.TRANSPARENT);
+ }
+
+ /**
+ * Append space.
+ *
+ * @param size The size of space.
+ * @param color The color of space.
+ * @return the single {@link SpannableStringUtils} instance
+ */
+ public SpannableStringUtils appendSpace(@IntRange(from = 0) final int size, @ColorInt final int color) {
+ apply(mTypeSpace);
+ spaceSize = size;
+ spaceColor = color;
+ return this;
+ }
+
+ private void apply(final int type) {
+ applyLast();
+ mType = type;
+ }
+
+ public SpannableStringBuilder get() {
+ return mBuilder;
+ }
+
+ /**
+ * Create the span string.
+ *
+ * @return the span string
+ */
+ public SpannableStringBuilder create() {
+ applyLast();
+ if (mTextView != null) {
+ mTextView.setText(mBuilder);
+ }
+ isCreated = true;
+ return mBuilder;
+ }
+
+ private void applyLast() {
+ if (isCreated) {
+ return;
+ }
+ if (mType == mTypeCharSequence) {
+ updateCharCharSequence();
+ } else if (mType == mTypeImage) {
+ updateImage();
+ } else if (mType == mTypeSpace) {
+ updateSpace();
+ }
+ setDefault();
+ }
+
+ private void updateCharCharSequence() {
+ if (mText.length() == 0) return;
+ int start = mBuilder.length();
+ if (start == 0 && lineHeight != -1) {// bug of LineHeightSpan when first line
+ mBuilder.append(Character.toString((char) 2))
+ .append("\n")
+ .setSpan(new AbsoluteSizeSpan(0), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ start = 2;
+ }
+ mBuilder.append(mText);
+ int end = mBuilder.length();
+ if (verticalAlign != -1) {
+ mBuilder.setSpan(new VerticalAlignSpan(verticalAlign), start, end, flag);
+ }
+ if (foregroundColor != COLOR_DEFAULT) {
+ mBuilder.setSpan(new ForegroundColorSpan(foregroundColor), start, end, flag);
+ }
+ if (backgroundColor != COLOR_DEFAULT) {
+ mBuilder.setSpan(new BackgroundColorSpan(backgroundColor), start, end, flag);
+ }
+ if (first != -1) {
+ mBuilder.setSpan(new LeadingMarginSpan.Standard(first, rest), start, end, flag);
+ }
+ if (quoteColor != COLOR_DEFAULT) {
+ mBuilder.setSpan(
+ new CustomQuoteSpan(quoteColor, stripeWidth, quoteGapWidth),
+ start,
+ end,
+ flag
+ );
+ }
+ if (bulletColor != COLOR_DEFAULT) {
+ mBuilder.setSpan(
+ new CustomBulletSpan(bulletColor, bulletRadius, bulletGapWidth),
+ start,
+ end,
+ flag
+ );
+ }
+ if (fontSize != -1) {
+ mBuilder.setSpan(new AbsoluteSizeSpan(fontSize, fontSizeIsDp), start, end, flag);
+ }
+ if (proportion != -1) {
+ mBuilder.setSpan(new RelativeSizeSpan(proportion), start, end, flag);
+ }
+ if (xProportion != -1) {
+ mBuilder.setSpan(new ScaleXSpan(xProportion), start, end, flag);
+ }
+ if (lineHeight != -1) {
+ mBuilder.setSpan(new CustomLineHeightSpan(lineHeight, alignLine), start, end, flag);
+ }
+ if (isStrikethrough) {
+ mBuilder.setSpan(new StrikethroughSpan(), start, end, flag);
+ }
+ if (isUnderline) {
+ mBuilder.setSpan(new UnderlineSpan(), start, end, flag);
+ }
+ if (isSuperscript) {
+ mBuilder.setSpan(new SuperscriptSpan(), start, end, flag);
+ }
+ if (isSubscript) {
+ mBuilder.setSpan(new SubscriptSpan(), start, end, flag);
+ }
+ if (isBold) {
+ mBuilder.setSpan(new StyleSpan(Typeface.BOLD), start, end, flag);
+ }
+ if (isItalic) {
+ mBuilder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, flag);
+ }
+ if (isBoldItalic) {
+ mBuilder.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, flag);
+ }
+ if (fontFamily != null) {
+ mBuilder.setSpan(new TypefaceSpan(fontFamily), start, end, flag);
+ }
+ if (typeface != null) {
+ mBuilder.setSpan(new CustomTypefaceSpan(typeface), start, end, flag);
+ }
+ if (alignment != null) {
+ mBuilder.setSpan(new AlignmentSpan.Standard(alignment), start, end, flag);
+ }
+ if (clickSpan != null) {
+ mBuilder.setSpan(clickSpan, start, end, flag);
+ }
+ if (url != null) {
+ mBuilder.setSpan(new URLSpan(url), start, end, flag);
+ }
+ if (blurRadius != -1) {
+ mBuilder.setSpan(
+ new MaskFilterSpan(new BlurMaskFilter(blurRadius, style)),
+ start,
+ end,
+ flag
+ );
+ }
+ if (shader != null) {
+ mBuilder.setSpan(new ShaderSpan(shader), start, end, flag);
+ }
+ if (shadowRadius != -1) {
+ mBuilder.setSpan(
+ new ShadowSpan(shadowRadius, shadowDx, shadowDy, shadowColor),
+ start,
+ end,
+ flag
+ );
+ }
+ if (spans != null) {
+ for (Object span : spans) {
+ mBuilder.setSpan(span, start, end, flag);
+ }
+ }
+ }
+
+ private void updateImage() {
+ int start = mBuilder.length();
+ mText = "
";
+ updateCharCharSequence();
+ int end = mBuilder.length();
+ if (imageBitmap != null) {
+ mBuilder.setSpan(new CustomImageSpan(imageBitmap, alignImage), start, end, flag);
+ } else if (imageDrawable != null) {
+ mBuilder.setSpan(new CustomImageSpan(imageDrawable, alignImage), start, end, flag);
+ } else if (imageUri != null) {
+ mBuilder.setSpan(new CustomImageSpan(imageUri, alignImage), start, end, flag);
+ } else if (imageResourceId != -1) {
+ mBuilder.setSpan(new CustomImageSpan(imageResourceId, alignImage), start, end, flag);
+ }
+ }
+
+ private void updateSpace() {
+ int start = mBuilder.length();
+ mText = "< >";
+ updateCharCharSequence();
+ int end = mBuilder.length();
+ mBuilder.setSpan(new SpaceSpan(spaceSize, spaceColor), start, end, flag);
+ }
+
+ static class VerticalAlignSpan extends ReplacementSpan {
+
+ static final int ALIGN_CENTER = 2;
+ static final int ALIGN_TOP = 3;
+
+ final int mVerticalAlignment;
+
+ VerticalAlignSpan(int verticalAlignment) {
+ mVerticalAlignment = verticalAlignment;
+ }
+
+ @Override
+ public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm) {
+ text = text.subSequence(start, end);
+ return (int) paint.measureText(text.toString());
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
+ text = text.subSequence(start, end);
+ Paint.FontMetricsInt fm = paint.getFontMetricsInt();
+// int need = height - (v + fm.descent - fm.ascent - spanstartv);
+// if (need > 0) {
+// if (mVerticalAlignment == ALIGN_TOP) {
+// fm.descent += need;
+// } else if (mVerticalAlignment == ALIGN_CENTER) {
+// fm.descent += need / 2;
+// fm.ascent -= need / 2;
+// } else {
+// fm.ascent -= need;
+// }
+// }
+// need = height - (v + fm.bottom - fm.top - spanstartv);
+// if (need > 0) {
+// if (mVerticalAlignment == ALIGN_TOP) {
+// fm.bottom += need;
+// } else if (mVerticalAlignment == ALIGN_CENTER) {
+// fm.bottom += need / 2;
+// fm.top -= need / 2;
+// } else {
+// fm.top -= need;
+// }
+// }
+
+ canvas.drawText(text.toString(), x, y - ((y + fm.descent + y + fm.ascent) / 2 - (bottom + top) / 2), paint);
+ }
+ }
+
+ static class CustomLineHeightSpan implements LineHeightSpan {
+
+ private final int height;
+
+ static final int ALIGN_CENTER = 2;
+ static final int ALIGN_TOP = 3;
+
+ final int mVerticalAlignment;
+ static Paint.FontMetricsInt sfm;
+
+ CustomLineHeightSpan(int height, int verticalAlignment) {
+ this.height = height;
+ mVerticalAlignment = verticalAlignment;
+ }
+
+ @Override
+ public void chooseHeight(final CharSequence text, final int start, final int end,
+ final int spanstartv, final int v, final Paint.FontMetricsInt fm) {
+ if (sfm == null) {
+ sfm = new Paint.FontMetricsInt();
+ sfm.top = fm.top;
+ sfm.ascent = fm.ascent;
+ sfm.descent = fm.descent;
+ sfm.bottom = fm.bottom;
+ sfm.leading = fm.leading;
+ } else {
+ fm.top = sfm.top;
+ fm.ascent = sfm.ascent;
+ fm.descent = sfm.descent;
+ fm.bottom = sfm.bottom;
+ fm.leading = sfm.leading;
+ }
+ int need = height - (v + fm.descent - fm.ascent - spanstartv);
+ if (need > 0) {
+ if (mVerticalAlignment == ALIGN_TOP) {
+ fm.descent += need;
+ } else if (mVerticalAlignment == ALIGN_CENTER) {
+ fm.descent += need / 2;
+ fm.ascent -= need / 2;
+ } else {
+ fm.ascent -= need;
+ }
+ }
+ need = height - (v + fm.bottom - fm.top - spanstartv);
+ if (need > 0) {
+ if (mVerticalAlignment == ALIGN_TOP) {
+ fm.bottom += need;
+ } else if (mVerticalAlignment == ALIGN_CENTER) {
+ fm.bottom += need / 2;
+ fm.top -= need / 2;
+ } else {
+ fm.top -= need;
+ }
+ }
+ if (end == ((Spanned) text).getSpanEnd(this)) {
+ sfm = null;
+ }
+// LogUtils.e(fm, sfm);
+ }
+ }
+
+ static class SpaceSpan extends ReplacementSpan {
+
+ private final int width;
+ private final Paint paint = new Paint();
+
+ private SpaceSpan(final int width) {
+ this(width, Color.TRANSPARENT);
+ }
+
+ private SpaceSpan(final int width, final int color) {
+ super();
+ this.width = width;
+ paint.setColor(color);
+ paint.setStyle(Paint.Style.FILL);
+ }
+
+ @Override
+ public int getSize(@NonNull final Paint paint, final CharSequence text,
+ @IntRange(from = 0) final int start,
+ @IntRange(from = 0) final int end,
+ @Nullable final Paint.FontMetricsInt fm) {
+ return width;
+ }
+
+ @Override
+ public void draw(@NonNull final Canvas canvas, final CharSequence text,
+ @IntRange(from = 0) final int start,
+ @IntRange(from = 0) final int end,
+ final float x, final int top, final int y, final int bottom,
+ @NonNull final Paint paint) {
+ canvas.drawRect(x, top, x + width, bottom, this.paint);
+ }
+ }
+
+ static class CustomQuoteSpan implements LeadingMarginSpan {
+
+ private final int color;
+ private final int stripeWidth;
+ private final int gapWidth;
+
+ private CustomQuoteSpan(final int color, final int stripeWidth, final int gapWidth) {
+ super();
+ this.color = color;
+ this.stripeWidth = stripeWidth;
+ this.gapWidth = gapWidth;
+ }
+
+ public int getLeadingMargin(final boolean first) {
+ return stripeWidth + gapWidth;
+ }
+
+ public void drawLeadingMargin(final Canvas c, final Paint p, final int x, final int dir,
+ final int top, final int baseline, final int bottom,
+ final CharSequence text, final int start, final int end,
+ final boolean first, final Layout layout) {
+ Paint.Style style = p.getStyle();
+ int color = p.getColor();
+
+ p.setStyle(Paint.Style.FILL);
+ p.setColor(this.color);
+
+ c.drawRect(x, top, x + dir * stripeWidth, bottom, p);
+
+ p.setStyle(style);
+ p.setColor(color);
+ }
+ }
+
+ static class CustomBulletSpan implements LeadingMarginSpan {
+
+ private final int color;
+ private final int radius;
+ private final int gapWidth;
+
+ private Path sBulletPath = null;
+
+ private CustomBulletSpan(final int color, final int radius, final int gapWidth) {
+ this.color = color;
+ this.radius = radius;
+ this.gapWidth = gapWidth;
+ }
+
+ public int getLeadingMargin(final boolean first) {
+ return 2 * radius + gapWidth;
+ }
+
+ public void drawLeadingMargin(final Canvas c, final Paint p, final int x, final int dir,
+ final int top, final int baseline, final int bottom,
+ final CharSequence text, final int start, final int end,
+ final boolean first, final Layout l) {
+ if (((Spanned) text).getSpanStart(this) == start) {
+ Paint.Style style = p.getStyle();
+ int oldColor = 0;
+ oldColor = p.getColor();
+ p.setColor(color);
+ p.setStyle(Paint.Style.FILL);
+ if (c.isHardwareAccelerated()) {
+ if (sBulletPath == null) {
+ sBulletPath = new Path();
+ // Bullet is slightly better to avoid aliasing artifacts on mdpi devices.
+ sBulletPath.addCircle(0.0f, 0.0f, radius, Path.Direction.CW);
+ }
+ c.save();
+ c.translate(x + dir * radius, (top + bottom) / 2.0f);
+ c.drawPath(sBulletPath, p);
+ c.restore();
+ } else {
+ c.drawCircle(x + dir * radius, (top + bottom) / 2.0f, radius, p);
+ }
+ p.setColor(oldColor);
+ p.setStyle(style);
+ }
+ }
+ }
+
+ @SuppressLint("ParcelCreator")
+ static class CustomTypefaceSpan extends TypefaceSpan {
+
+ private final Typeface newType;
+
+ private CustomTypefaceSpan(final Typeface type) {
+ super("");
+ newType = type;
+ }
+
+ @Override
+ public void updateDrawState(final TextPaint textPaint) {
+ apply(textPaint, newType);
+ }
+
+ @Override
+ public void updateMeasureState(final TextPaint paint) {
+ apply(paint, newType);
+ }
+
+ private void apply(final Paint paint, final Typeface tf) {
+ int oldStyle;
+ Typeface old = paint.getTypeface();
+ if (old == null) {
+ oldStyle = 0;
+ } else {
+ oldStyle = old.getStyle();
+ }
+
+ int fake = oldStyle & ~tf.getStyle();
+ if ((fake & Typeface.BOLD) != 0) {
+ paint.setFakeBoldText(true);
+ }
+
+ if ((fake & Typeface.ITALIC) != 0) {
+ paint.setTextSkewX(-0.25f);
+ }
+
+ paint.getShader();
+
+ paint.setTypeface(tf);
+ }
+ }
+
+ static class CustomImageSpan extends CustomDynamicDrawableSpan {
+ private Drawable mDrawable;
+ private Uri mContentUri;
+ private int mResourceId;
+
+ private CustomImageSpan(final Bitmap b, final int verticalAlignment) {
+ super(verticalAlignment);
+ mDrawable = new BitmapDrawable(BaseApplication.context.getResources(), b);
+ mDrawable.setBounds(
+ 0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()
+ );
+ }
+
+ private CustomImageSpan(final Drawable d, final int verticalAlignment) {
+ super(verticalAlignment);
+ mDrawable = d;
+ mDrawable.setBounds(
+ 0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()
+ );
+ }
+
+ private CustomImageSpan(final Uri uri, final int verticalAlignment) {
+ super(verticalAlignment);
+ mContentUri = uri;
+ }
+
+ private CustomImageSpan(@DrawableRes final int resourceId, final int verticalAlignment) {
+ super(verticalAlignment);
+ mResourceId = resourceId;
+ }
+
+ @Override
+ public Drawable getDrawable() {
+ Drawable drawable = null;
+ if (mDrawable != null) {
+ drawable = mDrawable;
+ } else if (mContentUri != null) {
+ Bitmap bitmap;
+ try {
+ InputStream is =
+ BaseApplication.context.getContentResolver().openInputStream(mContentUri);
+ bitmap = BitmapFactory.decodeStream(is);
+ drawable = new BitmapDrawable(BaseApplication.context.getResources(), bitmap);
+ drawable.setBounds(
+ 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()
+ );
+ if (is != null) {
+ is.close();
+ }
+ } catch (Exception e) {
+ Log.e("sms", "Failed to loaded content " + mContentUri, e);
+ }
+ } else {
+ try {
+ drawable = ContextCompat.getDrawable(BaseApplication.context, mResourceId);
+ drawable.setBounds(
+ 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()
+ );
+ } catch (Exception e) {
+ Log.e("sms", "Unable to find resource: " + mResourceId);
+ }
+ }
+ return drawable;
+ }
+ }
+
+ static abstract class CustomDynamicDrawableSpan extends ReplacementSpan {
+
+ static final int ALIGN_BOTTOM = 0;
+
+ static final int ALIGN_BASELINE = 1;
+
+ static final int ALIGN_CENTER = 2;
+
+ static final int ALIGN_TOP = 3;
+
+ final int mVerticalAlignment;
+
+ private CustomDynamicDrawableSpan() {
+ mVerticalAlignment = ALIGN_BOTTOM;
+ }
+
+ private CustomDynamicDrawableSpan(final int verticalAlignment) {
+ mVerticalAlignment = verticalAlignment;
+ }
+
+ public abstract Drawable getDrawable();
+
+ @Override
+ public int getSize(@NonNull final Paint paint, final CharSequence text,
+ final int start, final int end, final Paint.FontMetricsInt fm) {
+ Drawable d = getCachedDrawable();
+ Rect rect = d.getBounds();
+ if (fm != null) {
+ int lineHeight = fm.bottom - fm.top;
+ if (lineHeight < rect.height()) {
+ if (mVerticalAlignment == ALIGN_TOP) {
+ fm.top = fm.top;
+ fm.bottom = rect.height() + fm.top;
+ } else if (mVerticalAlignment == ALIGN_CENTER) {
+ fm.top = -rect.height() / 2 - lineHeight / 4;
+ fm.bottom = rect.height() / 2 - lineHeight / 4;
+ } else {
+ fm.top = -rect.height() + fm.bottom;
+ fm.bottom = fm.bottom;
+ }
+ fm.ascent = fm.top;
+ fm.descent = fm.bottom;
+ }
+ }
+ return rect.right;
+ }
+
+ @Override
+ public void draw(@NonNull final Canvas canvas, final CharSequence text,
+ final int start, final int end, final float x,
+ final int top, final int y, final int bottom, @NonNull final Paint paint) {
+ Drawable d = getCachedDrawable();
+ Rect rect = d.getBounds();
+ canvas.save();
+ float transY;
+ int lineHeight = bottom - top;
+ if (rect.height() < lineHeight) {
+ if (mVerticalAlignment == ALIGN_TOP) {
+ transY = top;
+ } else if (mVerticalAlignment == ALIGN_CENTER) {
+ transY = (bottom + top - rect.height()) / 2;
+ } else if (mVerticalAlignment == ALIGN_BASELINE) {
+ transY = y - rect.height();
+ } else {
+ transY = bottom - rect.height();
+ }
+ canvas.translate(x, transY);
+ } else {
+ canvas.translate(x, top);
+ }
+ d.draw(canvas);
+ canvas.restore();
+ }
+
+ private Drawable getCachedDrawable() {
+ WeakReference wr = mDrawableRef;
+ Drawable d = null;
+ if (wr != null) {
+ d = wr.get();
+ }
+ if (d == null) {
+ d = getDrawable();
+ mDrawableRef = new WeakReference<>(d);
+ }
+ return d;
+ }
+
+ private WeakReference mDrawableRef;
+ }
+
+ static class ShaderSpan extends CharacterStyle implements UpdateAppearance {
+ private Shader mShader;
+
+ private ShaderSpan(final Shader shader) {
+ this.mShader = shader;
+ }
+
+ @Override
+ public void updateDrawState(final TextPaint tp) {
+ tp.setShader(mShader);
+ }
+ }
+
+ static class ShadowSpan extends CharacterStyle implements UpdateAppearance {
+ private float radius;
+ private float dx, dy;
+ private int shadowColor;
+
+ private ShadowSpan(final float radius,
+ final float dx,
+ final float dy,
+ final int shadowColor) {
+ this.radius = radius;
+ this.dx = dx;
+ this.dy = dy;
+ this.shadowColor = shadowColor;
+ }
+
+ @Override
+ public void updateDrawState(final TextPaint tp) {
+ tp.setShadowLayer(radius, dx, dy, shadowColor);
+ }
+ }
+
+ private static class SerializableSpannableStringBuilder extends SpannableStringBuilder
+ implements Serializable {
+
+ private static final long serialVersionUID = 4909567650765875771L;
+ }
+}
\ No newline at end of file