diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..39fb081
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..96cc43e
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..e7bedf3
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..4f9005a
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..4c78841
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..e24555f
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..f364207
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,31 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.3.3'
+ classpath 'me.tatarka:gradle-retrolambda:3.4.0'
+ classpath 'org.greenrobot:greendao-gradle-plugin:3.2.1'
+ classpath 'com.jakewharton:butterknife-gradle-plugin:8.5.1'
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ maven { url 'https://jitpack.io' }
+ mavenCentral()
+ maven {
+ url "https://maven.google.com"
+ }
+ maven {
+ url 'https://dl.bintray.com/hpdx/maven/'
+ }
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..aac7c9b
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..d166add
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Sep 07 13:57:53 CST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..938b446
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':widget'
diff --git a/widget/.gitignore b/widget/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/widget/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/widget/build.gradle b/widget/build.gradle
new file mode 100644
index 0000000..275a11a
--- /dev/null
+++ b/widget/build.gradle
@@ -0,0 +1,94 @@
+apply plugin: 'com.android.library'
+apply plugin: 'me.tatarka.retrolambda'
+
+android {
+ compileSdkVersion 26
+ buildToolsVersion "26.0.1"
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 26
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ targetCompatibility 1.8
+ sourceCompatibility 1.8
+ }
+
+ configurations {
+ all*.exclude module: 'PhotoView' //去除重复依赖库
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+ compile 'com.android.support:appcompat-v7:26.+'
+ testCompile 'junit:junit:4.12'
+
+ compile 'com.android.support:recyclerview-v7:26.+'
+ compile 'com.android.support:design:26.+'
+
+ compile 'com.github.Cutta:GifView:1.1'
+ compile 'com.makeramen:roundedimageview:2.3.0'//圆角
+ compile 'com.jude:rollviewpager:1.4.6'
+ compile 'com.commit451:PhotoView:1.2.4'
+ compile 'com.davemorrissey.labs:subsampling-scale-image-view:3.6.0'
+ compile 'com.github.bumptech.glide:glide:4.1.1'
+ annotationProcessor 'com.github.bumptech.glide:compiler:4.1.1'
+ //主页底部导航栏
+ compile 'com.ashokvarma.android:bottom-navigation-bar:1.3.0'
+
+ //http://www.recyclerview.org/
+ compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.9'
+
+ compile 'com.contrarywind:Android-PickerView:3.2.4'
+
+ compile 'com.github.johnkil.print:print:1.2.2'
+
+ compile 'me.zhanghai.android.materialprogressbar:library:1.3.0'
+
+ //底部弹出菜单
+ //compile 'com.github.Kennyc1012:BottomSheet:2.3.1'
+ compile 'me.shaohui:bottomdialog:1.1.9'
+
+ compile 'com.jakewharton:butterknife:8.5.1'
+ //输入框
+ compile 'com.mylhyl:circleDialog:2.1.6'
+ compile 'com.youth.banner:banner:1.4.9'
+ compile 'com.github.yaozs:ImageShowPicker:1.0.0'
+ compile 'com.zhihu.android:matisse:0.4.3'
+ //动图的imageview
+ compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.7'
+ compile 'com.rengwuxian.materialedittext:library:2.1.4'
+ //自动大小textview
+ compile 'me.grantland:autofittextview:0.2.+'
+ compile 'com.github.pinguo-zhouwei:CustomPopwindow:2.0.0'
+ //自增textview
+ compile 'com.github.chaychan:PowerfulViewLibrary:1.2.1'
+ //侧滑删除
+ compile 'com.github.mcxtzhang:SwipeDelMenuLayout:V1.2.2'
+ //九宫格
+ compile 'cn.bingoogolapple:bga-photopicker:1.2.3@aar'
+ compile 'cn.bingoogolapple:bga-adapter:1.1.8@aar'
+ //九宫格
+ compile 'com.lqr.ninegridimageview:library:1.0.0'
+ compile 'pub.devrel:easypermissions:0.1.9'
+ //可以拖动的rv
+ compile 'com.yanzhenjie:recyclerview-swipe:1.1.1'
+ //Android在桌面图标上显示角标
+ compile 'com.anbetter:badger-helper:1.0.0'
+}
diff --git a/widget/proguard-rules.pro b/widget/proguard-rules.pro
new file mode 100644
index 0000000..41afecb
--- /dev/null
+++ b/widget/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in D:\Android\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/widget/src/androidTest/java/com/hzecool/widget/ExampleInstrumentedTest.java b/widget/src/androidTest/java/com/hzecool/widget/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..af59bb1
--- /dev/null
+++ b/widget/src/androidTest/java/com/hzecool/widget/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.hzecool.widget;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.hzecool.widget.test", appContext.getPackageName());
+ }
+}
diff --git a/widget/src/main/AndroidManifest.xml b/widget/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..23a3cce
--- /dev/null
+++ b/widget/src/main/AndroidManifest.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/widget/src/main/java/com/hzecool/widget/ActionSheetDialog.java b/widget/src/main/java/com/hzecool/widget/ActionSheetDialog.java
new file mode 100644
index 0000000..788f220
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/ActionSheetDialog.java
@@ -0,0 +1,510 @@
+package com.hzecool.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ArrayRes;
+import android.support.annotation.StringRes;
+import android.support.annotation.StyleRes;
+import android.support.v7.app.AlertDialog;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 底部弹出选择列表框
+ * Created by tutu on 2017/4/21.
+ */
+
+public class ActionSheetDialog extends AlertDialog {
+ private static final String TAG = "ActionSheetDialog";
+
+ protected ActionSheetDialog(Context context) {
+ super(context);
+ }
+
+ protected ActionSheetDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
+ super(context, cancelable, cancelListener);
+ }
+
+ protected ActionSheetDialog(Context context, @StyleRes int themeResId) {
+ super(context, themeResId);
+
+ }
+
+ @Override
+ public Button getButton(int whichButton) {
+ switch (whichButton) {
+
+ }
+ return super.getButton(whichButton);
+ }
+
+ public static class ActionSheetBuilder extends AlertDialog.Builder {
+ private Context mContext;
+ private String mTitle;
+ private String mMessage;
+ private String mNegativeText;
+ private String mPositiveText;
+ private boolean mCancelable;
+ private List mActionSheetItems;
+ private OnClickListener mNegativeClickListener;
+ private OnClickListener mPositiveClickListener;
+ private ActionSheetDialog mActionSheetDialog;
+
+ //Attributes
+ //Title attrs
+ private int mTitleTextColor;
+ private int mTitleTextSize;
+ private int mTitleHeight;
+ private Drawable mTitleDivider;
+ private int mTitleDividerInset;
+ private int mTitleDividerHeight;
+ //Message attrs
+ private int mMessageTextColor;
+ private int mMessageTextSize;
+ private int mMessageHeight;
+ private Drawable mMessageDivider;
+ private int mMessageDividerInset;
+ private int mMessageDividerHeight;
+ //Item attrs
+ private int mItemTextColor;
+ private int mItemTextSize;
+ private int mItemHeight;
+ private Drawable mItemDivider;
+ private int mItemDividerInset;
+ private int mItemDividerHeight;
+ //Positive button attrs
+ private int mPositiveTextColor;
+ private int mPositiveTextSize;
+ private int mPositiveHeight;
+ //Cancel button attrs
+ private int mCancelTextColor;
+ private int mCancelTextSize;
+ private int mCancelHeight;
+ private int mCancelTopMargin;
+ private Drawable mCancelBackground;
+ //Dialog attrs
+ private int mLayoutMargins;
+ private int mSheetMargins;
+ private Drawable mContentBackground;
+ private int mWindowAnimationId;
+ private static final int DEFAULT_VALUE = -1;
+
+
+ public ActionSheetBuilder setmItemTextColor(int mItemTextColor) {
+ this.mItemTextColor = mItemTextColor;
+
+ return this;
+ }
+
+ public ActionSheetBuilder(Context context) {
+ super(context);
+ mContext = context;
+ mActionSheetItems = new ArrayList<>();
+ TypedArray defaultTypedArray = context.obtainStyledAttributes(R.style.ActionSheetDialogBase, R.styleable.ActionSheetDialog);
+ if (null != defaultTypedArray) {
+ initDefaultAttributes(defaultTypedArray);
+ defaultTypedArray.recycle();
+ }
+ }
+
+ public ActionSheetBuilder(Context context, int themeResId) {
+ this(context);
+ TypedArray typedArray = context.obtainStyledAttributes(themeResId, R.styleable.ActionSheetDialog);
+ if (null != typedArray) {
+ initAttributes(typedArray);
+ typedArray.recycle();
+ }
+
+ }
+
+ private void initAttributes(TypedArray typedArray) {
+ if (null != typedArray) {
+ mTitleTextColor = typedArray.getColor(R.styleable.ActionSheetDialog_titleTextColor, mTitleTextColor);
+ mTitleTextSize = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_titleTextSize, mTitleTextSize);
+ mTitleHeight = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_titleHeight, mTitleHeight);
+ mTitleDivider = typedArray.getDrawable(R.styleable.ActionSheetDialog_titleDivider);
+ mTitleDividerInset = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_titleDividerInset, mTitleDividerInset);
+ mTitleDividerHeight = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_titleDividerHeight, mTitleDividerHeight);
+
+ mMessageTextColor = typedArray.getColor(R.styleable.ActionSheetDialog_messageTextColor, mMessageTextColor);
+ mMessageTextSize = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_messageTextSize, mMessageTextSize);
+ mMessageHeight = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_messageHeight, mMessageHeight);
+ mMessageDivider = typedArray.getDrawable(R.styleable.ActionSheetDialog_messageDivider);
+ mMessageDividerInset = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_messageDividerInset, mMessageDividerInset);
+ mMessageDividerHeight = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_messageDividerHeight, mMessageDividerHeight);
+
+ mItemTextColor = typedArray.getColor(R.styleable.ActionSheetDialog_itemTextColor, mItemTextColor);
+ mItemTextSize = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_itemTextSize, mItemTextSize);
+ mItemHeight = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_itemHeight, mItemHeight);
+ mItemDivider = typedArray.getDrawable(R.styleable.ActionSheetDialog_itemDivider);
+ mItemDividerInset = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_itemDividerInset, mItemDividerInset);
+ mItemDividerHeight = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_itemDividerHeight, mItemDividerHeight);
+
+ mPositiveTextColor = typedArray.getColor(R.styleable.ActionSheetDialog_positiveTextColor, mPositiveTextColor);
+ mPositiveTextSize = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_positiveTextSize, mPositiveTextSize);
+ mPositiveHeight = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_positiveHeight, mPositiveHeight);
+
+ mCancelTextColor = typedArray.getColor(R.styleable.ActionSheetDialog_cancelTextColor, mCancelTextColor);
+ mCancelTextSize = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_cancelTextSize, mCancelTextSize);
+ mCancelHeight = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_cancelHeight, mCancelHeight);
+ mCancelBackground = typedArray.getDrawable(R.styleable.ActionSheetDialog_cancelBackground);
+ mCancelTopMargin = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_cancelTopMargins, mCancelTopMargin);
+
+ mSheetMargins = typedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_sheetMargins, mSheetMargins);
+ mContentBackground = typedArray.getDrawable(R.styleable.ActionSheetDialog_contentBackground);
+ mWindowAnimationId = typedArray.getResourceId(R.styleable.ActionSheetDialog_windowAnimations, mWindowAnimationId);
+ }
+ }
+
+ private void initDefaultAttributes(TypedArray defaultTypedArray) {
+ if (null != defaultTypedArray) {
+ mTitleTextColor = defaultTypedArray.getColor(R.styleable.ActionSheetDialog_titleTextColor, DEFAULT_VALUE);
+ mTitleTextSize = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_titleTextSize, DEFAULT_VALUE);
+ mTitleHeight = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_titleHeight, DEFAULT_VALUE);
+ mTitleDivider = defaultTypedArray.getDrawable(R.styleable.ActionSheetDialog_titleDivider);
+ mTitleDividerInset = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_titleDividerInset, DEFAULT_VALUE);
+ mTitleDividerHeight = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_titleDividerHeight, DEFAULT_VALUE);
+
+ mMessageTextColor = defaultTypedArray.getColor(R.styleable.ActionSheetDialog_messageTextColor, DEFAULT_VALUE);
+ mMessageTextSize = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_messageTextSize, DEFAULT_VALUE);
+ mMessageHeight = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_messageHeight, DEFAULT_VALUE);
+ mMessageDivider = defaultTypedArray.getDrawable(R.styleable.ActionSheetDialog_messageDivider);
+ mMessageDividerInset = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_messageDividerInset, DEFAULT_VALUE);
+ mMessageDividerHeight = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_messageDividerHeight, DEFAULT_VALUE);
+
+ mItemTextColor = defaultTypedArray.getColor(R.styleable.ActionSheetDialog_itemTextColor, DEFAULT_VALUE);
+ mItemTextSize = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_itemTextSize, DEFAULT_VALUE);
+ mItemHeight = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_itemHeight, DEFAULT_VALUE);
+ mItemDivider = defaultTypedArray.getDrawable(R.styleable.ActionSheetDialog_itemDivider);
+ mItemDividerInset = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_itemDividerInset, DEFAULT_VALUE);
+ mItemDividerHeight = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_itemDividerHeight, DEFAULT_VALUE);
+
+ mPositiveTextColor = defaultTypedArray.getColor(R.styleable.ActionSheetDialog_positiveTextColor, DEFAULT_VALUE);
+ mPositiveTextSize = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_positiveTextSize, DEFAULT_VALUE);
+ mPositiveHeight = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_positiveHeight, DEFAULT_VALUE);
+
+ mCancelTextColor = defaultTypedArray.getColor(R.styleable.ActionSheetDialog_cancelTextColor, DEFAULT_VALUE);
+ mCancelTextSize = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_cancelTextSize, DEFAULT_VALUE);
+ mCancelHeight = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_cancelHeight, DEFAULT_VALUE);
+ mCancelBackground = defaultTypedArray.getDrawable(R.styleable.ActionSheetDialog_cancelBackground);
+ mCancelTopMargin = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_cancelTopMargins, DEFAULT_VALUE);
+
+ mSheetMargins = defaultTypedArray.getDimensionPixelSize(R.styleable.ActionSheetDialog_sheetMargins, DEFAULT_VALUE);
+ mContentBackground = defaultTypedArray.getDrawable(R.styleable.ActionSheetDialog_contentBackground);
+ mWindowAnimationId = defaultTypedArray.getResourceId(R.styleable.ActionSheetDialog_windowAnimations, DEFAULT_VALUE);
+
+ }
+ }
+
+
+ @Override
+ public ActionSheetBuilder setCancelable(boolean cancelable) {
+ mCancelable = cancelable;
+ return this;
+ }
+
+ @Override
+ public ActionSheetBuilder setMessage(CharSequence message) {
+ mMessage = (String) message;
+ return this;
+ }
+
+ @Override
+ public ActionSheetBuilder setMessage(@StringRes int messageId) {
+ mMessage = mContext.getString(messageId);
+ return this;
+ }
+
+ @Override
+ public ActionSheetBuilder setTitle(CharSequence title) {
+ mTitle = (String) title;
+ return this;
+ }
+
+ @Override
+ public ActionSheetBuilder setTitle(@StringRes int titleId) {
+ mTitle = mContext.getString(titleId);
+ return this;
+ }
+
+ @Override
+ public ActionSheetBuilder setNegativeButton(CharSequence text, OnClickListener listener) {
+ mNegativeText = (String) text;
+ mNegativeClickListener = listener;
+ mCancelable = true;
+ return this;
+ }
+
+ @Override
+ public ActionSheetBuilder setPositiveButton(CharSequence text, OnClickListener listener) {
+ mPositiveText = (String) text;
+ mPositiveClickListener = listener;
+ return this;
+ }
+
+ @Override
+ public ActionSheetBuilder setItems(CharSequence[] items, OnClickListener listener) {
+ for (int i = 0; i < items.length; i++) {
+ ActionSheetItem item = new ActionSheetItem((String) items[i], listener);
+ mActionSheetItems.add(item);
+ }
+ return this;
+ }
+
+ @Override
+ public ActionSheetBuilder setItems(@ArrayRes int itemsId, OnClickListener listener) {
+ this.setItems(mContext.getResources().getStringArray(itemsId), listener);
+ return this;
+ }
+
+ @Override
+ public ActionSheetDialog create() {
+ mActionSheetDialog = new ActionSheetDialog(mContext);
+ Window window = mActionSheetDialog.getWindow();
+ window.setGravity(Gravity.BOTTOM);
+ window.setWindowAnimations(mWindowAnimationId);
+ WindowManager.LayoutParams params = window.getAttributes();
+ params.y = dpToPx(mSheetMargins);
+ params.x = 0;
+ WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+
+ int width = windowManager.getDefaultDisplay().getWidth();
+ params.width = width - 2 * mSheetMargins;
+ Drawable drawable = new ColorDrawable();
+ drawable.setAlpha(0);
+ window.setBackgroundDrawable(drawable);
+ mActionSheetDialog.setCancelable(mCancelable);
+ if (mCancelable) {
+ mActionSheetDialog.setCanceledOnTouchOutside(true);
+ }
+ initViews();
+ return mActionSheetDialog;
+ }
+
+ TextView mTitleView;
+ ImageView mTitleDividerView;
+ TextView mMessageView;
+ ImageView mMessageDividerView;
+ LinearLayout mSheetItemContainer;
+ LinearLayout mContentPanel;
+ TextView mCancelView;
+ TextView mPositiveView;
+ SheetItemOnClickListener mSheetItemOnClickListener = new SheetItemOnClickListener();
+
+ private void initViews() {
+ View rootView = LayoutInflater.from(mContext)
+ .inflate(R.layout.layout_action_sheet_dialog, null);
+
+ mContentPanel = (LinearLayout) rootView.findViewById(R.id.content_panel);
+ mTitleView = (TextView) rootView.findViewById(R.id.tv_title);
+ mTitleDividerView = (ImageView) rootView.findViewById(R.id.title_divider);
+ mMessageView = (TextView) rootView.findViewById(R.id.tv_message);
+ mMessageDividerView = (ImageView) rootView.findViewById(R.id.message_divider);
+ mSheetItemContainer = (LinearLayout) rootView.findViewById(R.id.scrollView_sheet_list);
+ mCancelView = (TextView) rootView.findViewById(R.id.tv_cancel);
+ if (null != mContentBackground) {
+ mContentPanel.setBackground(mContentBackground);
+ }
+ handleTitle();
+ handleMessage();
+ handleContent();
+ handleCancel();
+ handlePositive();
+
+ mActionSheetDialog.setView(rootView);
+
+
+ }
+
+ private ImageView createDivider(Drawable background, int inset) {
+ ImageView divider = new ImageView(mContext);
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ Log.d(TAG, "createDivider: inset = " + inset);
+ params.setMargins(inset, 0, inset, 0);
+ divider.setLayoutParams(params);
+ divider.setBackground(background);
+ divider.setMinimumHeight(mItemDividerHeight);
+ return divider;
+ }
+
+ private void handlePositive() {
+ if (null != mPositiveText) {
+ mPositiveView = new TextView(mContext);
+ mPositiveView.setGravity(Gravity.CENTER);
+ ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ mPositiveView.setLayoutParams(params);
+ //mPositiveView.setPadding(0, dpToPx(5.0f), 0, dpToPx(5.0f));
+ mPositiveView.setText(mPositiveText);
+ mPositiveView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPositiveTextSize);
+ mPositiveView.setTextColor(mPositiveTextColor);
+ mPositiveView.setTag(AlertDialog.BUTTON_POSITIVE);
+ mPositiveView.setOnClickListener(mSheetItemOnClickListener);
+ mPositiveView.setMinHeight(mPositiveHeight);
+ mSheetItemContainer.addView(createDivider(mItemDivider, mItemDividerInset));
+ mSheetItemContainer.addView(mPositiveView);
+ }
+
+ }
+
+ private void handleCancel() {
+ if (null != mCancelView) {
+ if (mCancelable) {
+ mCancelView.setMinHeight(mCancelHeight);
+ mCancelView.setTextColor(mCancelTextColor);
+ mCancelView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCancelTextSize);
+ if (null != mCancelBackground) {
+ mCancelView.setBackground(mCancelBackground);
+ }
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ params.setMargins(0, mCancelTopMargin, 0, 0);
+ mCancelView.setLayoutParams(params);
+ if (null != mNegativeText) {
+ mCancelView.setText(mNegativeText);
+ }
+ mCancelView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (null != mNegativeClickListener) {
+ mNegativeClickListener.onClick(mActionSheetDialog, AlertDialog.BUTTON_NEGATIVE);
+ }
+ mActionSheetDialog.dismiss();
+ }
+ });
+ }
+ }
+ }
+
+ private void handleContent() {
+ if (null == mActionSheetItems || mActionSheetItems.isEmpty()) {
+ mSheetItemContainer.setVisibility(View.GONE);
+ } else {
+ for (int i = 0, size = mActionSheetItems.size(); i < size; i++) {
+ ActionSheetItem item = mActionSheetItems.get(i);
+ TextView sheetItemView = new TextView(mContext);
+ sheetItemView.setGravity(Gravity.CENTER);
+ ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ sheetItemView.setLayoutParams(params);
+ //sheetItemView.setPadding(0, dpToPx(5.0f), 0, dpToPx(5.0f));
+ sheetItemView.setText(item.text);
+ sheetItemView.setMinHeight(mItemHeight);
+ sheetItemView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mItemTextSize);
+ sheetItemView.setTextColor(mItemTextColor);
+ sheetItemView.setTag(i);
+ sheetItemView.setOnClickListener(mSheetItemOnClickListener);
+ mSheetItemContainer.addView(sheetItemView);
+ if (i < (size - 1)) {
+ mSheetItemContainer.addView(createDivider(mItemDivider, mItemDividerInset));
+ }
+ }
+
+ }
+ }
+
+ private void handleMessage() {
+ if (null != mMessageView) {
+ if (null == mMessage) {
+ mMessageView.setVisibility(View.GONE);
+ mMessageDividerView.setVisibility(View.GONE);
+ } else {
+ mMessageDividerView.setBackground(mMessageDivider);
+ mMessageDividerView.setMinimumHeight(mMessageDividerHeight);
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ params.setMargins(mMessageDividerInset, 0, mMessageDividerInset, 0);
+ mMessageDividerView.setLayoutParams(params);
+
+ mMessageView.setMinHeight(mMessageHeight);
+ mMessageView.setGravity(Gravity.CENTER);
+ mMessageView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMessageTextSize);
+ mMessageView.setTextColor(mMessageTextColor);
+ mMessageView.setText(mMessage);
+ }
+ }
+ }
+
+ private void handleTitle() {
+ if (null != mTitleView) {
+ if (null == mTitle) {
+ mTitleView.setVisibility(View.GONE);
+ mTitleDividerView.setVisibility(View.GONE);
+ } else {
+ mTitleDividerView.setBackground(mTitleDivider);
+ mTitleDividerView.setMinimumHeight(mTitleDividerHeight);
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ params.setMargins(mTitleDividerInset, 0, mTitleDividerInset, 0);
+ mTitleDividerView.setLayoutParams(params);
+
+ mTitleView.setMinHeight(mTitleHeight);
+ mTitleView.setGravity(Gravity.CENTER);
+ mTitleView.setTextColor(mTitleTextColor);
+ mTitleView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTitleTextSize);
+ mTitleView.setText(mTitle);
+ }
+ }
+ }
+
+ public int dpToPx(float dp) {
+ float scale = mContext.getResources().getDisplayMetrics().density;
+ return (int) (dp * scale + 0.5f);
+ }
+
+ public int spToPx(float sp) {
+ float scale = mContext.getResources().getDisplayMetrics().scaledDensity;
+ return (int) (sp * scale + 0.5f);
+ }
+
+
+ static class ActionSheetItem {
+
+ String text;
+ OnClickListener listener;
+
+ public ActionSheetItem(String text, OnClickListener listener) {
+ this.text = text;
+ this.listener = listener;
+ }
+ }
+
+ public interface ActionSheetItemClickListener {
+ void onClick(int position);
+ }
+
+ private class SheetItemOnClickListener implements View.OnClickListener {
+
+ @Override
+ public void onClick(View v) {
+
+ int tag = (int) v.getTag();
+ Log.d(TAG, "onClick: tag = " + tag);
+ if (BUTTON_POSITIVE == tag) {
+ if (null != mPositiveClickListener) {
+ mPositiveClickListener.onClick(mActionSheetDialog, BUTTON_POSITIVE);
+ mActionSheetDialog.dismiss();
+ }
+ mActionSheetDialog.dismiss();
+ } else {
+ mActionSheetItems.get(tag).listener.onClick(mActionSheetDialog, tag);
+ }
+ }
+ }
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/BalanceEditText.java b/widget/src/main/java/com/hzecool/widget/BalanceEditText.java
new file mode 100644
index 0000000..e602404
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/BalanceEditText.java
@@ -0,0 +1,80 @@
+package com.hzecool.widget;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.EditText;
+
+import java.math.BigDecimal;
+
+/**
+ * Created by wangzhiguo on 2017/6/8
+ */
+public class BalanceEditText extends android.support.v7.widget.AppCompatEditText {
+ public BalanceEditText(Context context) {
+ super(context);
+ }
+
+ public BalanceEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public BalanceEditText(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ /**
+ * 字符串转人民币
+ *
+ * @param value
+ * @return
+ */
+ public static String str2Rmb(String value) {
+ double money = Double.parseDouble(value);
+ BigDecimal bigDecimal = BigDecimal.valueOf(money);
+ return "¥" + bigDecimal.setScale(2,BigDecimal.ROUND_HALF_UP);
+ }
+
+ /**
+ * double 类型 转人民币
+ *
+ * @param value
+ * @return
+ */
+ public static String db2Rmb(double value) {
+ try {
+ return "¥" + BigDecimal.valueOf(value).setScale(2,BigDecimal.ROUND_HALF_UP);
+ } catch (NumberFormatException ex) {
+ throw new NumberFormatException();
+ }
+ }
+
+ public void setText(String text){
+ super.setText(str2Rmb(text));
+ }
+
+ public void setText(double text){
+ super.setText(db2Rmb(text));
+ }
+
+ public String getTextValue(){
+ return removeRmbStr(super.getText().toString());
+ }
+
+ public double getDoubleValue(){
+ if (TextUtils.isEmpty(this.getTextValue())){
+ return 0.00;
+ }
+ return Double.parseDouble(this.getTextValue());
+ }
+
+ private String removeRmbStr(@NonNull String text) {
+ return text.replace("¥", "");
+ }
+
+
+ public void setTextZero(){
+ this.setText(0.00);
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/BalanceTextView.java b/widget/src/main/java/com/hzecool/widget/BalanceTextView.java
new file mode 100644
index 0000000..28492d8
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/BalanceTextView.java
@@ -0,0 +1,93 @@
+package com.hzecool.widget;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import java.math.BigDecimal;
+
+/**
+ * Created by wangzhiguo on 2017/6/8
+ */
+public class BalanceTextView extends android.support.v7.widget.AppCompatTextView {
+
+ private double showText;
+ public BalanceTextView(Context context) {
+ super(context);
+ }
+
+ public BalanceTextView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public BalanceTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ /**
+ * 字符串转人民币
+ *
+ * @param value
+ * @return
+ */
+ public static String str2Rmb(String value) {
+ double money = Double.parseDouble(value);
+ BigDecimal bigDecimal = BigDecimal.valueOf(money);
+ return "¥" + bigDecimal.setScale(2,BigDecimal.ROUND_HALF_UP);
+ }
+
+ /**
+ * double 类型 转人民币
+ *
+ * @param value
+ * @return
+ */
+ public static String db2Rmb(double value) {
+ try {
+ return "¥" + new BigDecimal(value).setScale(2,BigDecimal.ROUND_HALF_UP).toString();
+ } catch (NumberFormatException ex) {
+ throw new NumberFormatException();
+ }
+ }
+
+ public void setText(@NonNull String text){
+ super.setText(str2Rmb(text));
+ }
+
+ public String getText(){
+ return removeRmbStr(super.getText().toString());
+ }
+
+ public void setText(@NonNull double text){
+ super.setText(db2Rmb(text));
+ }
+
+ public double getDoubleValue(){
+ return Double.parseDouble(this.getText());
+ }
+
+ private String removeRmbStr(@NonNull String text) {
+ return text.replace("¥", "");
+ }
+
+
+ public double getShowText(){
+ if (isLessZero){
+ return new BigDecimal(removeRmbStr(super.getText().toString())).multiply(new BigDecimal(-1)).floatValue();
+ }else {
+ return new BigDecimal(removeRmbStr(super.getText().toString())).multiply(new BigDecimal(1)).floatValue();
+ }
+ }
+
+ private boolean isLessZero = false;
+
+ public void setShowText(double text){
+ if (text <= 0){
+ isLessZero = true;
+ }else if (text > 0){
+ isLessZero = false;
+ }
+ setText(Math.abs(text));
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/BottomNavigationView.java b/widget/src/main/java/com/hzecool/widget/BottomNavigationView.java
new file mode 100644
index 0000000..c115dfd
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/BottomNavigationView.java
@@ -0,0 +1,877 @@
+package com.hzecool.widget;
+
+import android.content.Context;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.design.internal.BottomNavigationItemView;
+import android.support.design.internal.BottomNavigationMenuView;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.util.SparseIntArray;
+import android.util.TypedValue;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+
+/**
+ * Created by tutu on 2017/5/2.
+ * 引用自 https://github.com/ittianyu/BottomNavigationViewEx#zh
+ */
+public class BottomNavigationView extends android.support.design.widget.BottomNavigationView {
+ // used for animation
+ private int mShiftAmount;
+ private float mScaleUpFactor;
+ private float mScaleDownFactor;
+ private boolean animationRecord;
+ private float mLargeLabelSize;
+ private float mSmallLabelSize;
+ private boolean visibilityTextSizeRecord;
+ private boolean visibilityHeightRecord;
+ private int mItemHeight;
+ // used for animation end
+
+ // used for setupWithViewPager
+ private ViewPager mViewPager;
+ private MyOnNavigationItemSelectedListener mMyOnNavigationItemSelectedListener;
+ private BottomNavigationViewExOnPageChangeListener mPageChangeListener;
+ private BottomNavigationMenuView mMenuView;
+ private BottomNavigationItemView[] mButtons;
+ // used for setupWithViewPager end
+
+ public BottomNavigationView(Context context) {
+ super(context);
+ }
+
+ public BottomNavigationView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public BottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ /**
+ * change the visibility of icon
+ *
+ * @param visibility
+ */
+ public void setIconVisibility(boolean visibility) {
+ /*
+ 1. get field in this class
+ private final BottomNavigationMenuView mMenuView;
+
+ 2. get field in mButtons
+ private BottomNavigationItemView[] mButtons;
+
+ 3. get mIcon in mButtons
+ private ImageView mIcon
+
+ 4. set mIcon visibility gone
+
+ 5. change mItemHeight to only text size in mMenuView
+ */
+ // 1. get mMenuView
+ final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
+ // 2. get mButtons
+ BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
+ // 3. get mIcon in mButtons
+ for (BottomNavigationItemView button : mButtons) {
+ ImageView mIcon = getField(button.getClass(), button, "mIcon");
+ // 4. set mIcon visibility gone
+ mIcon.setVisibility(visibility ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ // 5. change mItemHeight to only text size in mMenuView
+ if (!visibility) {
+ // if not record mItemHeight
+ if (!visibilityHeightRecord) {
+ visibilityHeightRecord = true;
+ mItemHeight = getItemHeight();
+ }
+
+ // change mItemHeight
+ BottomNavigationItemView button = mButtons[0];
+ if (null != button) {
+ final ImageView mIcon = getField(button.getClass(), button, "mIcon");
+// System.out.println("mIcon.getMeasuredHeight():" + mIcon.getMeasuredHeight());
+ if (null != mIcon) {
+ mIcon.post(new Runnable() {
+ @Override
+ public void run() {
+// System.out.println("mIcon.getMeasuredHeight():" + mIcon.getMeasuredHeight());
+ setItemHeight(mItemHeight - mIcon.getMeasuredHeight());
+ }
+ });
+ }
+ }
+ } else {
+ // if not record the mItemHeight, we need do nothing.
+ if (!visibilityHeightRecord)
+ return;
+
+ // restore it
+ setItemHeight(mItemHeight);
+ }
+
+ mMenuView.updateMenuView();
+ }
+
+ /**
+ * change the visibility of text
+ *
+ * @param visibility
+ */
+ public void setTextVisibility(boolean visibility) {
+ /*
+ 1. get field in this class
+ private final BottomNavigationMenuView mMenuView;
+
+ 2. get field in mButtons
+ private BottomNavigationItemView[] mButtons;
+
+ 3. set text size in mButtons
+ private final TextView mLargeLabel
+ private final TextView mSmallLabel
+
+ 4. change mItemHeight to only icon size in mMenuView
+ */
+ // 1. get mMenuView
+ BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
+ // 2. get mButtons
+ BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
+ // 3. change field mShiftingMode value in mButtons
+ for (BottomNavigationItemView button : mButtons) {
+ TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel");
+ TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel");
+
+ if (!visibility) {
+ // if not record the font size, record it
+ if (!visibilityTextSizeRecord && !animationRecord) {
+ visibilityTextSizeRecord = true;
+ mLargeLabelSize = mLargeLabel.getTextSize();
+ mSmallLabelSize = mSmallLabel.getTextSize();
+ }
+
+ // if not visitable, set font size to 0
+ mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, 0);
+ mSmallLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, 0);
+
+ } else {
+ // if not record the font size, we need do nothing.
+ if (!visibilityTextSizeRecord)
+ break;
+
+ // restore it
+ mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLargeLabelSize);
+ mSmallLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSmallLabelSize);
+ }
+ }
+
+ // 4 change mItemHeight to only icon size in mMenuView
+ if (!visibility) {
+ // if not record mItemHeight
+ if (!visibilityHeightRecord) {
+ visibilityHeightRecord = true;
+ mItemHeight = getItemHeight();
+ }
+
+ // change mItemHeight to only icon size in mMenuView
+ // private final int mItemHeight;
+
+ // change mItemHeight
+// System.out.println("mLargeLabel.getMeasuredHeight():" + getFontHeight(mSmallLabelSize));
+ setItemHeight(mItemHeight - getFontHeight(mSmallLabelSize));
+
+ } else {
+ // if not record the mItemHeight, we need do nothing.
+ if (!visibilityHeightRecord)
+ return;
+ // restore mItemHeight
+ setItemHeight(mItemHeight);
+ }
+
+ mMenuView.updateMenuView();
+ }
+
+ /**
+ * get text height by font size
+ *
+ * @param fontSize
+ * @return
+ */
+ private static int getFontHeight(float fontSize) {
+ Paint paint = new Paint();
+ paint.setTextSize(fontSize);
+ Paint.FontMetrics fm = paint.getFontMetrics();
+ return (int) Math.ceil(fm.descent - fm.top) + 2;
+ }
+
+ /**
+ * enable or disable click item animation(text scale and icon move animation in no item shifting mode)
+ *
+ * @param enable It means the text won't scale and icon won't move when active it in no item shifting mode if false.
+ */
+ public void enableAnimation(boolean enable) {
+ /*
+ 1. get field in this class
+ private final BottomNavigationMenuView mMenuView;
+
+ 2. get field in mButtons
+ private BottomNavigationItemView[] mButtons;
+
+ 3. chang mShiftAmount to 0 in mButtons
+ private final int mShiftAmount
+
+ change mScaleUpFactor and mScaleDownFactor to 1f in mButtons
+ private final float mScaleUpFactor
+ private final float mScaleDownFactor
+
+ 4. change label font size in mButtons
+ private final TextView mLargeLabel
+ private final TextView mSmallLabel
+ */
+
+ // 1. get mMenuView
+ BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
+ // 2. get mButtons
+ BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
+ // 3. change field mShiftingMode value in mButtons
+ for (BottomNavigationItemView button : mButtons) {
+ TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel");
+ TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel");
+
+ // if disable animation, need animationRecord the source value
+ if (!enable) {
+ if (!animationRecord) {
+ animationRecord = true;
+ mShiftAmount = getField(button.getClass(), button, "mShiftAmount");
+ mScaleUpFactor = getField(button.getClass(), button, "mScaleUpFactor");
+ mScaleDownFactor = getField(button.getClass(), button, "mScaleDownFactor");
+
+ mLargeLabelSize = mLargeLabel.getTextSize();
+ mSmallLabelSize = mSmallLabel.getTextSize();
+
+// System.out.println("mShiftAmount:" + mShiftAmount + " mScaleUpFactor:"
+// + mScaleUpFactor + " mScaleDownFactor:" + mScaleDownFactor
+// + " mLargeLabel:" + mLargeLabelSize + " mSmallLabel:" + mSmallLabelSize);
+ }
+ // disable
+ setField(button.getClass(), button, "mShiftAmount", 0);
+ setField(button.getClass(), button, "mScaleUpFactor", 1);
+ setField(button.getClass(), button, "mScaleDownFactor", 1);
+
+ // let the mLargeLabel font size equal to mSmallLabel
+ mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSmallLabelSize);
+
+ // debug start
+// mLargeLabelSize = mLargeLabel.getTextSize();
+// System.out.println("mLargeLabel:" + mLargeLabelSize);
+ // debug end
+
+ } else {
+ // haven't change the value. It means it was the first call this method. So nothing need to do.
+ if (!animationRecord)
+ return;
+ // enable animation
+ setField(button.getClass(), button, "mShiftAmount", mShiftAmount);
+ setField(button.getClass(), button, "mScaleUpFactor", mScaleUpFactor);
+ setField(button.getClass(), button, "mScaleDownFactor", mScaleDownFactor);
+ // restore
+ mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLargeLabelSize);
+ }
+ }
+ mMenuView.updateMenuView();
+ }
+
+ /**
+ * enable the shifting mode for navigation
+ *
+ * @param enable It will has a shift animation if true. Otherwise all items are the same width.
+ */
+ public void enableShiftingMode(boolean enable) {
+ /*
+ 1. get field in this class
+ private final BottomNavigationMenuView mMenuView;
+
+ 2. change field mShiftingMode value in mMenuView
+ private boolean mShiftingMode = true;
+ */
+ // 1. get mMenuView
+ BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
+ // 2. change field mShiftingMode value in mMenuView
+ setField(mMenuView.getClass(), mMenuView, "mShiftingMode", enable);
+
+ mMenuView.updateMenuView();
+ }
+
+ /**
+ * enable the shifting mode for each item
+ *
+ * @param enable It will has a shift animation for item if true. Otherwise the item text always be shown.
+ */
+ public void enableItemShiftingMode(boolean enable) {
+ /*
+ 1. get field in this class
+ private final BottomNavigationMenuView mMenuView;
+
+ 2. get field in this mMenuView
+ private BottomNavigationItemView[] mButtons;
+
+ 3. change field mShiftingMode value in mButtons
+ private boolean mShiftingMode = true;
+ */
+ // 1. get mMenuView
+ BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
+ // 2. get mButtons
+ BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
+ // 3. change field mShiftingMode value in mButtons
+ for (BottomNavigationItemView button : mButtons) {
+ setField(button.getClass(), button, "mShiftingMode", enable);
+ }
+ mMenuView.updateMenuView();
+ }
+
+ /**
+ * get the current checked item position
+ *
+ * @return index of item, start from 0.
+ */
+ public int getCurrentItem() {
+ /*
+ 1. get field in this class
+ private final BottomNavigationMenuView mMenuView;
+
+ 2. get field in mMenuView
+ private BottomNavigationItemView[] mButtons;
+
+ 3. get menu and traverse it to get the checked one
+ */
+
+ // 1. get mMenuView
+// BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
+ // 2. get mButtons
+ BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
+ // 3. get menu and traverse it to get the checked one
+ Menu menu = getMenu();
+ for (int i = 0; i < mButtons.length; i++) {
+ if (menu.getItem(i).isChecked()) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * get menu item position in menu
+ *
+ * @param item
+ * @return position if success, -1 otherwise
+ */
+ public int getMenuItemPosition(MenuItem item) {
+ // get item id
+ int itemId = item.getItemId();
+ // get meunu
+ Menu menu = getMenu();
+ int size = menu.size();
+ for (int i = 0; i < size; i++) {
+ if (menu.getItem(i).getItemId() == itemId) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * set the current checked item
+ *
+ * @param item start from 0.
+ */
+ public void setCurrentItem(int item) {
+ // check bounds
+ if (item < 0 || item >= getMaxItemCount()) {
+ throw new ArrayIndexOutOfBoundsException("item is out of bounds, we expected 0 - "
+ + (getMaxItemCount() - 1) + ". Actually " + item);
+ }
+
+ /*
+ 1. get field in this class
+ private final BottomNavigationMenuView mMenuView;
+
+ 2. get field in mMenuView
+ private BottomNavigationItemView[] mButtons;
+ private final OnClickListener mOnClickListener;
+
+ 3. call mOnClickListener.onClick();
+ */
+ // 1. get mMenuView
+ BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
+ // 2. get mButtons
+ BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
+ // get mOnClickListener
+ OnClickListener mOnClickListener = getField(mMenuView.getClass(), mMenuView, "mOnClickListener");
+
+// System.out.println("mMenuView:" + mMenuView + " mButtons:" + mButtons + " mOnClickListener" + mOnClickListener);
+ // 3. call mOnClickListener.onClick();
+ mOnClickListener.onClick(mButtons[item]);
+
+ }
+
+ /**
+ * get OnNavigationItemSelectedListener
+ *
+ * @return
+ */
+ public OnNavigationItemSelectedListener getOnNavigationItemSelectedListener() {
+ // private OnNavigationItemSelectedListener mListener;
+ OnNavigationItemSelectedListener mListener = getField(getClass().getSuperclass(), this, "mListener");
+ return mListener;
+ }
+
+ @Override
+ public void setOnNavigationItemSelectedListener(@Nullable OnNavigationItemSelectedListener listener) {
+ // if not set up with view pager, the same with father
+ if (null == mMyOnNavigationItemSelectedListener) {
+ super.setOnNavigationItemSelectedListener(listener);
+ return;
+ }
+
+ mMyOnNavigationItemSelectedListener.setOnNavigationItemSelectedListener(listener);
+ }
+
+ /**
+ * get private mMenuView
+ *
+ * @return
+ */
+ private BottomNavigationMenuView getBottomNavigationMenuView() {
+ if (null == mMenuView)
+ mMenuView = getField(getClass().getSuperclass(), this, "mMenuView");
+ return mMenuView;
+ }
+
+ /**
+ * get private mButtons in mMenuView
+ *
+ * @return
+ */
+ public BottomNavigationItemView[] getBottomNavigationItemViews() {
+ if (null != mButtons)
+ return mButtons;
+ /*
+ * 1 private final BottomNavigationMenuView mMenuView;
+ * 2 private BottomNavigationItemView[] mButtons;
+ */
+ BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
+ mButtons = getField(mMenuView.getClass(), mMenuView, "mButtons");
+ return mButtons;
+ }
+
+ /**
+ * get private mButton in mMenuView at position
+ *
+ * @param position
+ * @return
+ */
+ public BottomNavigationItemView getBottomNavigationItemView(int position) {
+ return getBottomNavigationItemViews()[position];
+ }
+
+ /**
+ * get icon at position
+ *
+ * @param position
+ * @return
+ */
+ public ImageView getIconAt(int position) {
+ /*
+ * 1 private final BottomNavigationMenuView mMenuView;
+ * 2 private BottomNavigationItemView[] mButtons;
+ * 3 private ImageView mIcon;
+ */
+ BottomNavigationItemView mButtons = getBottomNavigationItemView(position);
+ ImageView mIcon = getField(BottomNavigationItemView.class, mButtons, "mIcon");
+ return mIcon;
+ }
+
+ /**
+ * get small label at position
+ * Each item has tow label, one is large, another is small.
+ *
+ * @param position
+ * @return
+ */
+ public TextView getSmallLabelAt(int position) {
+ /*
+ * 1 private final BottomNavigationMenuView mMenuView;
+ * 2 private BottomNavigationItemView[] mButtons;
+ * 3 private final TextView mSmallLabel;
+ */
+ BottomNavigationItemView mButtons = getBottomNavigationItemView(position);
+ TextView mSmallLabel = getField(BottomNavigationItemView.class, mButtons, "mSmallLabel");
+ return mSmallLabel;
+ }
+
+ /**
+ * get large label at position
+ * Each item has tow label, one is large, another is small.
+ *
+ * @param position
+ * @return
+ */
+ public TextView getLargeLabelAt(int position) {
+ /*
+ * 1 private final BottomNavigationMenuView mMenuView;
+ * 2 private BottomNavigationItemView[] mButtons;
+ * 3 private final TextView mLargeLabel;
+ */
+ BottomNavigationItemView mButtons = getBottomNavigationItemView(position);
+ TextView mLargeLabel = getField(BottomNavigationItemView.class, mButtons, "mLargeLabel");
+ return mLargeLabel;
+ }
+
+ /**
+ * return item count
+ *
+ * @return
+ */
+ public int getItemCount() {
+ return getBottomNavigationItemViews().length;
+ }
+
+ /**
+ * set all item small TextView size
+ * Each item has tow label, one is large, another is small.
+ * Small one will be shown when item state is normal
+ * Large one will be shown when item checked.
+ *
+ * @param sp
+ */
+ public void setSmallTextSize(float sp) {
+ int count = getItemCount();
+ for (int i = 0; i < count; i++) {
+ getSmallLabelAt(i).setTextSize(sp);
+ }
+ mMenuView.updateMenuView();
+ }
+
+ /**
+ * set all item large TextView size
+ * Each item has tow label, one is large, another is small.
+ * Small one will be shown when item state is normal.
+ * Large one will be shown when item checked.
+ *
+ * @param sp
+ */
+ public void setLargeTextSize(float sp) {
+ int count = getItemCount();
+ for (int i = 0; i < count; i++) {
+ getLargeLabelAt(i).setTextSize(sp);
+ }
+ mMenuView.updateMenuView();
+ }
+
+ /**
+ * set all item large and small TextView size
+ * Each item has tow label, one is large, another is small.
+ * Small one will be shown when item state is normal
+ * Large one will be shown when item checked.
+ *
+ * @param sp
+ */
+ public void setTextSize(float sp) {
+ setLargeTextSize(sp);
+ setSmallTextSize(sp);
+ }
+
+ /**
+ * set item ImageView size which at position
+ *
+ * @param position position start from 0
+ * @param width in dp
+ * @param height in dp
+ */
+ public void setIconSizeAt(int position, float width, float height) {
+ ImageView icon = getIconAt(position);
+ // update size
+ ViewGroup.LayoutParams layoutParams = icon.getLayoutParams();
+ layoutParams.width = dp2px(getContext(), width);
+ layoutParams.height = dp2px(getContext(), height);
+ icon.setLayoutParams(layoutParams);
+
+ mMenuView.updateMenuView();
+ }
+
+ /**
+ * set all item ImageView size
+ *
+ * @param width in dp
+ * @param height in dp
+ */
+ public void setIconSize(float width, float height) {
+ int count = getItemCount();
+ for (int i = 0; i < count; i++) {
+ setIconSizeAt(i, width, height);
+ }
+ }
+
+ /**
+ * set menu item height
+ *
+ * @param height in px
+ */
+ public void setItemHeight(int height) {
+ // 1. get mMenuView
+ final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
+ // 2. set private final int mItemHeight in mMenuView
+ setField(mMenuView.getClass(), mMenuView, "mItemHeight", height);
+
+ mMenuView.updateMenuView();
+ }
+
+ /**
+ * get menu item height
+ *
+ * @return in px
+ */
+ public int getItemHeight() {
+ // 1. get mMenuView
+ final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
+ // 2. get private final int mItemHeight in mMenuView
+ return getField(mMenuView.getClass(), mMenuView, "mItemHeight");
+ }
+
+ /**
+ * dp to px
+ *
+ * @param context
+ * @param dpValue dp
+ * @return px
+ */
+ public static int dp2px(Context context, float dpValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (dpValue * scale + 0.5f);
+ }
+
+ /**
+ * set Typeface for all item TextView
+ *
+ * @attr ref android.R.styleable#TextView_typeface
+ * @attr ref android.R.styleable#TextView_textStyle
+ */
+ public void setTypeface(Typeface typeface, int style) {
+ int count = getItemCount();
+ for (int i = 0; i < count; i++) {
+ getLargeLabelAt(i).setTypeface(typeface, style);
+ getSmallLabelAt(i).setTypeface(typeface, style);
+ }
+ mMenuView.updateMenuView();
+ }
+
+ /**
+ * set Typeface for all item TextView
+ *
+ * @attr ref android.R.styleable#TextView_typeface
+ */
+ public void setTypeface(Typeface typeface) {
+ int count = getItemCount();
+ for (int i = 0; i < count; i++) {
+ getLargeLabelAt(i).setTypeface(typeface);
+ getSmallLabelAt(i).setTypeface(typeface);
+ }
+ mMenuView.updateMenuView();
+ }
+
+ /**
+ * get private filed in this specific object
+ *
+ * @param targetClass
+ * @param instance the filed owner
+ * @param fieldName
+ * @param
+ * @return field if success, null otherwise.
+ */
+ private T getField(Class targetClass, Object instance, String fieldName) {
+ try {
+ Field field = targetClass.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return (T) field.get(instance);
+ } catch (NoSuchFieldException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * change the field value
+ *
+ * @param targetClass
+ * @param instance the filed owner
+ * @param fieldName
+ * @param value
+ */
+ private void setField(Class targetClass, Object instance, String fieldName, Object value) {
+ try {
+ Field field = targetClass.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(instance, value);
+ } catch (NoSuchFieldException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This method will link the given ViewPager and this BottomNavigationViewEx together so that
+ * changes in one are automatically reflected in the other. This includes scroll state changes
+ * and clicks.
+ *
+ * @param viewPager
+ */
+ public void setupWithViewPager(@Nullable final ViewPager viewPager) {
+ setupWithViewPager(viewPager, false);
+ }
+
+ /**
+ * This method will link the given ViewPager and this BottomNavigationViewEx together so that
+ * changes in one are automatically reflected in the other. This includes scroll state changes
+ * and clicks.
+ *
+ * @param viewPager
+ * @param smoothScroll whether ViewPager changed with smooth scroll animation
+ */
+ public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean smoothScroll) {
+ if (mViewPager != null) {
+ // If we've already been setup with a ViewPager, remove us from it
+ if (mPageChangeListener != null) {
+ mViewPager.removeOnPageChangeListener(mPageChangeListener);
+ }
+ }
+
+ if (null == viewPager) {
+ mViewPager = null;
+ super.setOnNavigationItemSelectedListener(null);
+ return;
+ }
+
+ mViewPager = viewPager;
+
+ // Add our custom OnPageChangeListener to the ViewPager
+ if (mPageChangeListener == null) {
+ mPageChangeListener = new BottomNavigationViewExOnPageChangeListener(this);
+ }
+ viewPager.addOnPageChangeListener(mPageChangeListener);
+
+ // Now we'll add a navigation item selected listener to set ViewPager's current item
+ OnNavigationItemSelectedListener listener = getOnNavigationItemSelectedListener();
+ mMyOnNavigationItemSelectedListener = new MyOnNavigationItemSelectedListener(viewPager, this, smoothScroll, listener);
+ super.setOnNavigationItemSelectedListener(mMyOnNavigationItemSelectedListener);
+ }
+
+ /**
+ * A {@link ViewPager.OnPageChangeListener} class which contains the
+ * necessary calls back to the provided {@link BottomNavigationView} so that the tab position is
+ * kept in sync.
+ *
+ *
This class stores the provided BottomNavigationViewEx weakly, meaning that you can use
+ * {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener)
+ * addOnPageChangeListener(OnPageChangeListener)} without removing the listener and
+ * not cause a leak.
+ */
+ private static class BottomNavigationViewExOnPageChangeListener implements ViewPager.OnPageChangeListener {
+ private final WeakReference mBnveRef;
+
+ public BottomNavigationViewExOnPageChangeListener(BottomNavigationView bnve) {
+ mBnveRef = new WeakReference<>(bnve);
+ }
+
+ @Override
+ public void onPageScrollStateChanged(final int state) {
+ }
+
+ @Override
+ public void onPageScrolled(final int position, final float positionOffset,
+ final int positionOffsetPixels) {
+ }
+
+ @Override
+ public void onPageSelected(final int position) {
+ final BottomNavigationView bnve = mBnveRef.get();
+ if (null != bnve)
+ bnve.setCurrentItem(position);
+// Log.d("onPageSelected", "--------- position " + position + " ------------");
+ }
+ }
+
+ /**
+ * Decorate OnNavigationItemSelectedListener for setupWithViewPager
+ */
+ private static class MyOnNavigationItemSelectedListener implements OnNavigationItemSelectedListener {
+ private OnNavigationItemSelectedListener listener;
+ private final WeakReference viewPagerRef;
+ private boolean smoothScroll;
+ private SparseIntArray items;// used for change ViewPager selected item
+ private int previousPosition = -1;
+
+
+ MyOnNavigationItemSelectedListener(ViewPager viewPager, BottomNavigationView bnve, boolean smoothScroll, OnNavigationItemSelectedListener listener) {
+ this.viewPagerRef = new WeakReference<>(viewPager);
+ this.listener = listener;
+ this.smoothScroll = smoothScroll;
+
+ // create items
+ Menu menu = bnve.getMenu();
+ int size = menu.size();
+ items = new SparseIntArray(size);
+ for (int i = 0; i < size; i++) {
+ int itemId = menu.getItem(i).getItemId();
+ items.put(itemId, i);
+ }
+ }
+
+ public void setOnNavigationItemSelectedListener(OnNavigationItemSelectedListener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
+ int position = items.get(item.getItemId());
+ // only set item when item changed
+ if (previousPosition == position) {
+ return true;
+ }
+
+ // user listener
+ if (null != listener) {
+ boolean bool = listener.onNavigationItemSelected(item);
+ // if the selected is invalid, no need change the view pager
+ if (!bool)
+ return false;
+ }
+
+ // change view pager
+ ViewPager viewPager = viewPagerRef.get();
+ if (null == viewPager)
+ return false;
+
+ viewPager.setCurrentItem(items.get(item.getItemId()), smoothScroll);
+
+ // update previous position
+ previousPosition = position;
+
+ return true;
+ }
+
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/CheckImageView.java b/widget/src/main/java/com/hzecool/widget/CheckImageView.java
new file mode 100644
index 0000000..830067a
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/CheckImageView.java
@@ -0,0 +1,72 @@
+package com.hzecool.widget;
+
+import android.content.Context;
+import android.support.v7.widget.AppCompatImageView;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Checkable;
+
+/**
+ * Created by tutu on 2017/5/3.
+ */
+
+
+public class CheckImageView extends AppCompatImageView implements Checkable {
+ private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
+ private boolean mChecked;
+
+ public CheckImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public CheckImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CheckImageView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ if (checked != mChecked) {
+ mChecked = checked;
+ refreshDrawableState();
+ }
+ }
+
+ @Override
+ public boolean isChecked() {
+ return mChecked;
+ }
+
+ @Override
+ public void toggle() {
+ mChecked = !mChecked;
+ refreshDrawableState();
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setChecked(mChecked);
+ }
+
+ @Override
+ public int[] onCreateDrawableState(int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+ if (isChecked()) {
+ mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+ }
+ return drawableState;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setCheckable(true);
+ info.setChecked(mChecked);
+ }
+}
+
diff --git a/widget/src/main/java/com/hzecool/widget/ClearableEditText.java b/widget/src/main/java/com/hzecool/widget/ClearableEditText.java
new file mode 100644
index 0000000..6231a46
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/ClearableEditText.java
@@ -0,0 +1,120 @@
+package com.hzecool.widget;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Created by slh on 2017/3/1.
+ */
+
+public class ClearableEditText extends android.support.v7.widget.AppCompatEditText implements View.OnFocusChangeListener, TextWatcher {
+ /**
+ * 删除按钮的引用
+ */
+ private Drawable mClearDrawable;
+ /**
+ * 控件是否有焦点
+ */
+ private boolean hasFoucs;
+
+ public ClearableEditText(Context context) {
+ this(context, null);
+ }
+
+ public ClearableEditText(Context context, AttributeSet attrs) {
+ // 这里构造方法也很重要,不加这个很多属性不能再XML里面定义
+ this(context, attrs, android.R.attr.editTextStyle);
+ }
+
+ public ClearableEditText(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ private void init() {
+ // 获取EditText的DrawableRight,假如没有设置我们就使用默认的图片
+ mClearDrawable = getCompoundDrawables()[2];
+ if (mClearDrawable == null) {
+ // throw new
+ // NullPointerException("You can add drawableRight attribute in XML");
+ mClearDrawable = getResources().getDrawable(R.mipmap.ic_delete);
+ }
+
+ mClearDrawable.setBounds(0, 0, mClearDrawable.getIntrinsicWidth(), mClearDrawable.getIntrinsicHeight());
+ // 默认设置隐藏图标
+ setClearIconVisible(false);
+ // 设置焦点改变的监听
+ setOnFocusChangeListener(this);
+ // 设置输入框里面内容发生改变的监听
+ addTextChangedListener(this);
+ }
+
+ /**
+ * 因为我们不能直接给EditText设置点击事件,所以我们用记住我们按下的位置来模拟点击事件 当我们按下的位置 在 EditText的宽度 -
+ * 图标到控件右边的间距 - 图标的宽度 和 EditText的宽度 - 图标到控件右边的间距之间我们就算点击了图标,竖直方向就没有考虑
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ if (getCompoundDrawables()[2] != null) {
+
+ boolean touchable = event.getX() > (getWidth() - getTotalPaddingRight()) && (event.getX() < ((getWidth() - getPaddingRight())));
+
+ if (touchable) {
+ this.setText("");
+ }
+ }
+ }
+
+ return super.onTouchEvent(event);
+ }
+
+ /**
+ * 当ClearEditText焦点发生变化的时候,判断里面字符串长度设置清除图标的显示与隐藏
+ */
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ this.hasFoucs = hasFocus;
+ if (hasFocus) {
+ setClearIconVisible(getText().length() > 0);
+ } else {
+ setClearIconVisible(false);
+ }
+ }
+
+ /**
+ * 设置清除图标的显示与隐藏,调用setCompoundDrawables为EditText绘制上去
+ *
+ * @param visible
+ */
+ protected void setClearIconVisible(boolean visible) {
+ Drawable right = visible ? mClearDrawable : null;
+ setCompoundDrawables(getCompoundDrawables()[0], getCompoundDrawables()[1], right, getCompoundDrawables()[3]);
+ }
+
+ /**
+ * 当输入框里面内容发生变化的时候回调的方法
+ */
+ @Override
+ public void onTextChanged(CharSequence s, int start, int count, int after) {
+ if (hasFoucs) {
+ setClearIconVisible(s.length() > 0);
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/CountDownButton/CountDownButton.java b/widget/src/main/java/com/hzecool/widget/CountDownButton/CountDownButton.java
new file mode 100644
index 0000000..9ede67b
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/CountDownButton/CountDownButton.java
@@ -0,0 +1,214 @@
+package com.hzecool.widget.CountDownButton;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.CountDownTimer;
+import android.support.v7.widget.AppCompatButton;
+import android.util.AttributeSet;
+
+import com.hzecool.widget.R;
+
+import java.util.Locale;
+
+/**
+ * Created by tutu on 2017/3/16.
+ */
+
+public class CountDownButton extends AppCompatButton {
+
+ /**
+ * 默认时间间隔1000ms
+ */
+ private static final long DEFAULT_INTERVAL = 1000;
+ /**
+ * 默认时长60s
+ */
+ private static final long DEFAULT_COUNT = 60 * 1000;
+ /**
+ * 默认倒计时文字格式(显示秒数)
+ */
+ private static final String DEFAULT_COUNT_FORMAT = "%d";
+ /**
+ * 默认按钮文字 {@link #getText()}
+ */
+ private String mDefaultText;
+ /**
+ * 倒计时时长,单位为毫秒
+ */
+ private long mCount;
+ /**
+ * 时间间隔,单位为毫秒
+ */
+ private long mInterval;
+ /**
+ * 倒计时文字格式
+ */
+ private String mCountDownFormat = DEFAULT_COUNT_FORMAT;
+ /**
+ * 倒计时是否可用
+ */
+ private boolean mEnableCountDown = true;
+ /**
+ * 点击事件监听器
+ */
+ private OnClickListener onClickListener;
+
+ /**
+ * 倒计时
+ */
+ private CountDownTimer mCountDownTimer;
+
+ /**
+ * 是否正在执行倒计时
+ */
+ private boolean isCountDownNow;
+
+ private int enableFalseTextColor;
+ private int enableTextColor;
+
+ public CountDownButton(Context context) {
+ super(context);
+ }
+
+ public CountDownButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs);
+ }
+
+ public CountDownButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs);
+ }
+
+
+ private void init(Context context, AttributeSet attrs) {
+ // 获取自定义属性值
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CountDownButton);
+ mCountDownFormat = typedArray.getString(R.styleable.CountDownButton_countDownFormat);
+ enableFalseTextColor = typedArray.getColor(R.styleable.CountDownButton_enableFalseTextColor,getResources().getColor(R.color.white));
+ enableTextColor = typedArray.getColor(R.styleable.CountDownButton_enableTextColor,getResources().getColor(R.color.white));
+ if (typedArray.hasValue(R.styleable.CountDownButton_countDown)) {
+ mCount = (int) typedArray.getFloat(R.styleable.CountDownButton_countDown, DEFAULT_COUNT);
+ }
+ mInterval = (int) typedArray.getFloat(R.styleable.CountDownButton_countDownInterval, DEFAULT_INTERVAL);
+ mEnableCountDown = (mCount > mInterval) && typedArray.getBoolean(R.styleable.CountDownButton_enableCountDown, true);
+ typedArray.recycle();
+ // 初始化倒计时Timer
+ if (mCountDownTimer == null) {
+ mCountDownTimer = new CountDownTimer(mCount, mInterval) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ setText(String.format(Locale.CHINA, mCountDownFormat, millisUntilFinished / 1000));
+ }
+
+ @Override
+ public void onFinish() {
+ isCountDownNow = false;
+ setEnabled(true);
+ setClickable(true);
+ setText(mDefaultText);
+ setTextColor(enableTextColor);
+ }
+ };
+ }
+ }
+
+
+ @Override
+ public void setOnClickListener(OnClickListener onClickListener) {
+ super.setOnClickListener(onClickListener);
+ this.onClickListener = onClickListener;
+ }
+
+
+ public void start(){
+ if (mEnableCountDown){
+ setTextColor(enableFalseTextColor);
+ mDefaultText = getText().toString();
+ // 设置按钮不可点击
+ setEnabled(false);
+ setClickable(false);
+ // 开始倒计时
+ mCountDownTimer.start();
+ isCountDownNow = true;
+ }
+ }
+
+// @Override
+// public boolean onTouchEvent(MotionEvent event) {
+// switch (event.getAction()) {
+// case MotionEvent.ACTION_UP:
+// Rect rect = new Rect();
+// this.getGlobalVisibleRect(rect);
+// if (onClickListener != null && rect.contains((int) event.getRawX(), (int) event.getRawY())) {
+// onClickListener.onClick(this);
+// }
+// if (mEnableCountDown && rect.contains((int) event.getRawX(), (int) event.getRawY())) {
+// mDefaultText = getText().toString();
+// // 设置按钮不可点击
+// setEnabled(false);
+// setClickable(false);
+// // 开始倒计时
+// mCountDownTimer.start();
+// isCountDownNow = true;
+// }
+// break;
+// case MotionEvent.ACTION_MOVE:
+// break;
+// }
+// return super.onTouchEvent(event);
+// }
+
+ public void setEnableCountDown(boolean enableCountDown) {
+ this.mEnableCountDown = (mCount > mInterval) && enableCountDown;
+ }
+
+ public void setCountDownFormat(String countDownFormat) {
+ this.mCountDownFormat = countDownFormat;
+ }
+
+ public void setCount(long count) {
+ this.mCount = count;
+ }
+
+ public void setInterval(long interval) {
+ mInterval = interval;
+ }
+
+ /**
+ * 是否正在执行倒计时
+ *
+ * @return 倒计时期间返回true否则返回false
+ */
+ public boolean isCountDownNow() {
+ return isCountDownNow;
+ }
+
+ /**
+ * 设置倒计时数据
+ *
+ * @param count 时长
+ * @param interval 间隔
+ * @param countDownFormat 文字格式
+ */
+ public void setCountDown(long count, long interval, String countDownFormat) {
+ this.mCount = count;
+ this.mCountDownFormat = countDownFormat;
+ this.mInterval = interval;
+ setEnableCountDown(true);
+ }
+
+ /**
+ * 移除倒计时
+ */
+ public void removeCountDown() {
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ }
+ setTextColor(enableTextColor);
+ isCountDownNow = false;
+ setText(mDefaultText);
+ setEnabled(true);
+ setClickable(true);
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/FlowLayout.java b/widget/src/main/java/com/hzecool/widget/FlowLayout.java
new file mode 100644
index 0000000..be572c1
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/FlowLayout.java
@@ -0,0 +1,200 @@
+package com.hzecool.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 流式布局
+ * Created by wangzhiguo on 2017/6/19
+ */
+public class FlowLayout extends ViewGroup {
+
+
+ public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public FlowLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FlowLayout(Context context) {
+ this(context, null);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+ int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
+ int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
+ int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
+
+ // 如果是warp_content情况下,记录宽和高
+ int width = 0;
+ int height = 0;
+
+ // 记录每一行的宽度与高度
+ int lineWidth = 0;
+ int lineHeight = 0;
+
+ // 得到内部元素的个数
+ int cCount = getChildCount();
+
+ for (int i = 0; i < cCount; i++) {
+ // 通过索引拿到每一个子view
+ View child = getChildAt(i);
+ // 测量子View的宽和高,系统提供的measureChild
+ measureChild(child, widthMeasureSpec, heightMeasureSpec);
+ // 得到LayoutParams
+ MarginLayoutParams lp = (MarginLayoutParams) child
+ .getLayoutParams();
+
+ // 子View占据的宽度
+ int childWidth = child.getMeasuredWidth() + lp.leftMargin
+ + lp.rightMargin;
+ // 子View占据的高度
+ int childHeight = child.getMeasuredHeight() + lp.topMargin
+ + lp.bottomMargin;
+
+ // 换行 判断 当前的宽度大于 开辟新行
+ if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) {
+ // 对比得到最大的宽度
+ width = Math.max(width, lineWidth);
+ // 重置lineWidth
+ lineWidth = childWidth;
+ // 记录行高
+ height += lineHeight;
+ lineHeight = childHeight;
+ } else
+ // 未换行
+ {
+ // 叠加行宽
+ lineWidth += childWidth;
+ // 得到当前行最大的高度
+ lineHeight = Math.max(lineHeight, childHeight);
+ }
+ // 特殊情况,最后一个控件
+ if (i == cCount - 1) {
+ width = Math.max(lineWidth, width);
+ height += lineHeight;
+ }
+ }
+ setMeasuredDimension(
+ modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),
+ modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom()//
+ );
+
+ }
+
+ /**
+ * 存储所有的View
+ */
+ private List> mAllViews = new ArrayList>();
+ /**
+ * 每一行的高度
+ */
+ private List mLineHeight = new ArrayList();
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ mAllViews.clear();
+ mLineHeight.clear();
+
+ // 当前ViewGroup的宽度
+ int width = getWidth();
+
+ int lineWidth = 0;
+ int lineHeight = 0;
+
+ // 存放每一行的子view
+ List lineViews = new ArrayList();
+
+ int cCount = getChildCount();
+
+ for (int i = 0; i < cCount; i++) {
+ View child = getChildAt(i);
+ MarginLayoutParams lp = (MarginLayoutParams) child
+ .getLayoutParams();
+
+ int childWidth = child.getMeasuredWidth();
+ int childHeight = child.getMeasuredHeight();
+
+ // 如果需要换行
+ if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width - getPaddingLeft() - getPaddingRight()) {
+ // 记录LineHeight
+ mLineHeight.add(lineHeight);
+ // 记录当前行的Views
+ mAllViews.add(lineViews);
+
+ // 重置我们的行宽和行高
+ lineWidth = 0;
+ lineHeight = childHeight + lp.topMargin + lp.bottomMargin;
+ // 重置我们的View集合
+ lineViews = new ArrayList();
+ }
+ lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
+ lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
+ + lp.bottomMargin);
+ lineViews.add(child);
+
+ }// for end
+ // 处理最后一行
+ mLineHeight.add(lineHeight);
+ mAllViews.add(lineViews);
+
+ // 设置子View的位置
+
+ int left = getPaddingLeft();
+ int top = getPaddingTop();
+
+ // 行数
+ int lineNum = mAllViews.size();
+
+ for (int i = 0; i < lineNum; i++) {
+ // 当前行的所有的View
+ lineViews = mAllViews.get(i);
+ lineHeight = mLineHeight.get(i);
+
+ for (int j = 0; j < lineViews.size(); j++) {
+ View child = lineViews.get(j);
+ // 判断child的状态
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ MarginLayoutParams lp = (MarginLayoutParams) child
+ .getLayoutParams();
+
+ int lc = left + lp.leftMargin;
+ int tc = top + lp.topMargin;
+ int rc = lc + child.getMeasuredWidth();
+ int bc = tc + child.getMeasuredHeight();
+
+ // 为子View进行布局
+ child.layout(lc, tc, rc, bc);
+
+ left += child.getMeasuredWidth() + lp.leftMargin
+ + lp.rightMargin;
+ }
+ left = getPaddingLeft();
+ top += lineHeight;
+ }
+
+ }
+
+ /**
+ * 与当前ViewGroup对应的LayoutParams
+ */
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new MarginLayoutParams(getContext(), attrs);
+ }
+
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/RecycleViewDivider.java b/widget/src/main/java/com/hzecool/widget/RecycleViewDivider.java
new file mode 100644
index 0000000..b562e8b
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/RecycleViewDivider.java
@@ -0,0 +1,148 @@
+package com.hzecool.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+/**
+ * RecyclerView 分割线
+ * Created by tutu on 2017/4/19.
+ */
+
+public class RecycleViewDivider extends RecyclerView.ItemDecoration {
+ private Paint mPaint;
+ private Drawable mDivider;
+ private int mDividerHeight = 2;//分割线高度,默认为1px
+ private int mOrientation;//列表的方向:LinearLayoutManager.VERTICAL或LinearLayoutManager.HORIZONTAL
+ private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
+ private int marginLeft = 0;
+ private int marginRight = 0;
+
+ /**
+ * 默认分割线:高度为2px,颜色为灰色
+ *
+ * @param context
+ * @param orientation 列表方向
+ */
+ public RecycleViewDivider(Context context, int orientation) {
+ if (orientation != LinearLayoutManager.VERTICAL && orientation != LinearLayoutManager.HORIZONTAL) {
+ throw new IllegalArgumentException("请输入正确的参数!");
+ }
+ mOrientation = orientation;
+
+ final TypedArray a = context.obtainStyledAttributes(ATTRS);
+ mDivider = a.getDrawable(0);
+ a.recycle();
+ }
+
+ /**
+ * 自定义分割线
+ *
+ * @param context
+ * @param orientation 列表方向
+ * @param drawableId 分割线图片
+ */
+ public RecycleViewDivider(Context context, int orientation, int drawableId) {
+ this(context, orientation);
+ mDivider = ContextCompat.getDrawable(context, drawableId);
+ mDividerHeight = mDivider.getIntrinsicHeight();
+ }
+
+ /**
+ * 自定义分割线
+ *
+ * @param context
+ * @param orientation 列表方向
+ * @param dividerHeight 分割线高度
+ * @param dividerColor 分割线颜色
+ */
+ public RecycleViewDivider(Context context, int orientation, int dividerHeight, int dividerColor) {
+ this(context, orientation);
+ mDividerHeight = dividerHeight;
+ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mPaint.setColor(dividerColor);
+ mPaint.setStyle(Paint.Style.FILL);
+ }
+
+
+ public int getMarginLeft() {
+ return marginLeft;
+ }
+
+ public void setMarginLeft(int marginLeft) {
+ this.marginLeft = marginLeft;
+ }
+
+ public int getMarginRight() {
+ return marginRight;
+ }
+
+ public void setMarginRight(int marginRight) {
+ this.marginRight = marginRight;
+ }
+
+ //获取分割线尺寸
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+ super.getItemOffsets(outRect, view, parent, state);
+ outRect.set(0, 0, 0, mDividerHeight);
+ }
+
+ //绘制分割线
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ super.onDraw(c, parent, state);
+ if (mOrientation == LinearLayoutManager.VERTICAL) {
+ drawVertical(c, parent);
+ } else {
+ drawHorizontal(c, parent);
+ }
+ }
+
+ //绘制横向 item 分割线
+ private void drawHorizontal(Canvas canvas, RecyclerView parent) {
+ final int left = parent.getPaddingLeft();
+ final int right = parent.getMeasuredWidth() - parent.getPaddingRight();
+ final int childSize = parent.getChildCount();
+ for (int i = 0; i < childSize-1; i++) {
+ final View child = parent.getChildAt(i);
+ RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
+ final int top = child.getBottom() + layoutParams.bottomMargin;
+ final int bottom = top + mDividerHeight;
+ if (mDivider != null) {
+ mDivider.setBounds(left+marginLeft, top, right-marginRight, bottom);
+ mDivider.draw(canvas);
+ }
+ if (mPaint != null) {
+ canvas.drawRect(left+marginLeft, top, right-marginRight, bottom, mPaint);
+ }
+ }
+ }
+
+ //绘制纵向 item 分割线
+ private void drawVertical(Canvas canvas, RecyclerView parent) {
+ final int top = parent.getPaddingTop();
+ final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom();
+ final int childSize = parent.getChildCount();
+ for (int i = 0; i < childSize; i++) {
+ final View child = parent.getChildAt(i);
+ RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
+ final int left = child.getRight() + layoutParams.rightMargin;
+ final int right = left + mDividerHeight;
+ if (mDivider != null) {
+ mDivider.setBounds(left, top, right, bottom);
+ mDivider.draw(canvas);
+ }
+ if (mPaint != null) {
+ canvas.drawRect(left, top, right, bottom, mPaint);
+ }
+ }
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/WaveSideBar.java b/widget/src/main/java/com/hzecool/widget/WaveSideBar.java
new file mode 100644
index 0000000..21fe252
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/WaveSideBar.java
@@ -0,0 +1,398 @@
+package com.hzecool.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+import android.view.View;
+
+import java.util.Arrays;
+
+/**
+ * 列表添加索引
+ * add by tutu on 2017/4/12
+ * refer to https://github.com/gjiazhe/WaveSideBar
+ */
+public class WaveSideBar extends View {
+ private final static int DEFAULT_TEXT_SIZE = 14; // sp
+ private final static int DEFAULT_MAX_OFFSET = 80; //dp
+
+ private final static String[] DEFAULT_INDEX_ITEMS = {"A", "B", "C", "D", "E", "F", "G", "H", "I",
+ "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};
+
+ private String[] mIndexItems;
+
+ /**
+ * the index in {@link #mIndexItems} of the current selected index item,
+ * it's reset to -1 when the finger up
+ */
+ private int mCurrentIndex = -1;
+
+ /**
+ * Y coordinate of the point where finger is touching,
+ * the baseline is top of {@link #mStartTouchingArea}
+ * it's reset to -1 when the finger up
+ */
+ private float mCurrentY = -1;
+
+ private Paint mPaint;
+ private int mTextColor;
+ private float mTextSize;
+
+ /**
+ * the height of each index item
+ */
+ private float mIndexItemHeight;
+
+ /**
+ * offset of the current selected index item
+ */
+ private float mMaxOffset;
+
+ /**
+ * {@link #mStartTouching} will be set to true when {@link MotionEvent#ACTION_DOWN}
+ * happens in this area, and the side bar should start working.
+ */
+ private RectF mStartTouchingArea = new RectF();
+
+ /**
+ * height and width of {@link #mStartTouchingArea}
+ */
+ private float mBarHeight;
+ private float mBarWidth;
+
+ /**
+ * Flag that the finger is starting touching.
+ * If true, it means the {@link MotionEvent#ACTION_DOWN} happened but
+ * {@link MotionEvent#ACTION_UP} not yet.
+ */
+ private boolean mStartTouching = false;
+
+ /**
+ * if true, the {@link OnSelectIndexItemListener#onSelectIndexItem(String)}
+ * will not be called until the finger up.
+ * if false, it will be called when the finger down, up and move.
+ */
+ private boolean mLazyRespond = false;
+
+ /**
+ * the position of the side bar, default is {@link #POSITION_RIGHT}.
+ * You can set it to {@link #POSITION_LEFT} for people who use phone with left hand.
+ */
+ private int mSideBarPosition;
+ public static final int POSITION_RIGHT = 0;
+ public static final int POSITION_LEFT = 1;
+
+ /**
+ * the alignment of items, default is {@link #TEXT_ALIGN_CENTER}.
+ */
+ private int mTextAlignment;
+ public static final int TEXT_ALIGN_CENTER = 0;
+ public static final int TEXT_ALIGN_LEFT = 1;
+ public static final int TEXT_ALIGN_RIGHT = 2;
+
+
+ /**
+ * observe the current selected index item
+ */
+ private OnSelectIndexItemListener onSelectIndexItemListener;
+
+ /**
+ * the baseline of the first index item text to draw
+ */
+ private float mFirstItemBaseLineY;
+
+ /**
+ * for {@link #dp2px(int)} and {@link #sp2px(int)}
+ */
+ private DisplayMetrics mDisplayMetrics;
+
+
+ public WaveSideBar(Context context) {
+ this(context, null);
+ }
+
+ public WaveSideBar(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public WaveSideBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mDisplayMetrics = context.getResources().getDisplayMetrics();
+
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WaveSideBar);
+ mLazyRespond = typedArray.getBoolean(R.styleable.WaveSideBar_sidebar_lazy_respond, false);
+ mTextColor = typedArray.getColor(R.styleable.WaveSideBar_sidebar_text_color, Color.GRAY);
+ mTextSize = typedArray.getDimension(R.styleable.WaveSideBar_sidebar_text_size, sp2px(DEFAULT_TEXT_SIZE));
+ mMaxOffset = typedArray.getDimension(R.styleable.WaveSideBar_sidebar_max_offset, dp2px(DEFAULT_MAX_OFFSET));
+ mSideBarPosition = typedArray.getInt(R.styleable.WaveSideBar_sidebar_position, POSITION_RIGHT);
+ mTextAlignment = typedArray.getInt(R.styleable.WaveSideBar_sidebar_text_alignment, TEXT_ALIGN_CENTER);
+ typedArray.recycle();
+
+ mIndexItems = DEFAULT_INDEX_ITEMS;
+
+ initPaint();
+ }
+
+ private void initPaint() {
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setColor(mTextColor);
+ mPaint.setTextSize(mTextSize);
+ switch (mTextAlignment) {
+ case TEXT_ALIGN_CENTER:
+ mPaint.setTextAlign(Paint.Align.CENTER);
+ break;
+ case TEXT_ALIGN_LEFT:
+ mPaint.setTextAlign(Paint.Align.LEFT);
+ break;
+ case TEXT_ALIGN_RIGHT:
+ mPaint.setTextAlign(Paint.Align.RIGHT);
+ break;
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+
+ Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
+ mIndexItemHeight = fontMetrics.bottom - fontMetrics.top;
+ mBarHeight = mIndexItems.length * mIndexItemHeight;
+
+ // calculate the width of the longest text as the width of side bar
+ for (String indexItem : mIndexItems) {
+ mBarWidth = Math.max(mBarWidth, mPaint.measureText(indexItem));
+ }
+
+ float areaLeft = (mSideBarPosition == POSITION_LEFT) ? 0 : (width - mBarWidth - getPaddingRight());
+ float areaRight = (mSideBarPosition == POSITION_LEFT) ? (getPaddingLeft() + areaLeft + mBarWidth) : width;
+ float areaTop = height / 2 - mBarHeight / 2;
+ float areaBottom = areaTop + mBarHeight;
+ mStartTouchingArea.set(
+ areaLeft,
+ areaTop,
+ areaRight,
+ areaBottom);
+
+ // the baseline Y of the first item' text to draw
+ mFirstItemBaseLineY = (height / 2 - mIndexItems.length * mIndexItemHeight / 2)
+ + (mIndexItemHeight / 2 - (fontMetrics.descent - fontMetrics.ascent) / 2)
+ - fontMetrics.ascent;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ // draw each item
+ for (int i = 0, mIndexItemsLength = mIndexItems.length; i < mIndexItemsLength; i++) {
+ float baseLineY = mFirstItemBaseLineY + mIndexItemHeight * i;
+
+ // calculate the scale factor of the item to draw
+ float scale = getItemScale(i);
+
+ int alphaScale = (i == mCurrentIndex) ? (255) : (int) (255 * (1 - scale));
+ mPaint.setAlpha(alphaScale);
+
+ mPaint.setTextSize(mTextSize + mTextSize * scale);
+
+ float baseLineX = 0f;
+ if (mSideBarPosition == POSITION_LEFT) {
+ switch (mTextAlignment) {
+ case TEXT_ALIGN_CENTER:
+ baseLineX = getPaddingLeft() + mBarWidth / 2 + mMaxOffset * scale;
+ break;
+ case TEXT_ALIGN_LEFT:
+ baseLineX = getPaddingLeft() + mMaxOffset * scale;
+ break;
+ case TEXT_ALIGN_RIGHT:
+ baseLineX = getPaddingLeft() + mBarWidth + mMaxOffset * scale;
+ break;
+ }
+ } else {
+ switch (mTextAlignment) {
+ case TEXT_ALIGN_CENTER:
+ baseLineX = getWidth() - getPaddingRight() - mBarWidth / 2 - mMaxOffset * scale;
+ break;
+ case TEXT_ALIGN_RIGHT:
+ baseLineX = getWidth() - getPaddingRight() - mMaxOffset * scale;
+ break;
+ case TEXT_ALIGN_LEFT:
+ baseLineX = getWidth() - getPaddingRight() - mBarWidth - mMaxOffset * scale;
+ break;
+ }
+ }
+
+ // draw
+ canvas.drawText(
+ mIndexItems[i], //item text to draw
+ baseLineX, //baseLine X
+ baseLineY, // baseLine Y
+ mPaint);
+ }
+
+ // reset paint
+ mPaint.setAlpha(255);
+ mPaint.setTextSize(mTextSize);
+ }
+
+ /**
+ * calculate the scale factor of the item to draw
+ *
+ * @param index the index of the item in array {@link #mIndexItems}
+ * @return the scale factor of the item to draw
+ */
+ private float getItemScale(int index) {
+ float scale = 0;
+ if (mCurrentIndex != -1) {
+ float distance = Math.abs(mCurrentY - (mIndexItemHeight * index + mIndexItemHeight / 2)) / mIndexItemHeight;
+ scale = 1 - distance * distance / 16;
+ scale = Math.max(scale, 0);
+ }
+ return scale;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mIndexItems.length == 0) {
+ return super.onTouchEvent(event);
+ }
+
+ float eventY = event.getY();
+ float eventX = event.getX();
+ mCurrentIndex = getSelectedIndex(eventY);
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ if (mStartTouchingArea.contains(eventX, eventY)) {
+ mStartTouching = true;
+ if (!mLazyRespond && onSelectIndexItemListener != null) {
+ onSelectIndexItemListener.onSelectIndexItem(mIndexItems[mCurrentIndex]);
+ }
+ invalidate();
+ return true;
+ } else {
+ mCurrentIndex = -1;
+ return false;
+ }
+
+ case MotionEvent.ACTION_MOVE:
+ if (mStartTouching && !mLazyRespond && onSelectIndexItemListener != null) {
+ onSelectIndexItemListener.onSelectIndexItem(mIndexItems[mCurrentIndex]);
+ }
+ invalidate();
+ return true;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (mLazyRespond && onSelectIndexItemListener != null) {
+ onSelectIndexItemListener.onSelectIndexItem(mIndexItems[mCurrentIndex]);
+ }
+ mCurrentIndex = -1;
+ mStartTouching = false;
+ invalidate();
+ return true;
+ }
+
+ return super.onTouchEvent(event);
+ }
+
+ private int getSelectedIndex(float eventY) {
+ mCurrentY = eventY - (getHeight() / 2 - mBarHeight / 2);
+ if (mCurrentY <= 0) {
+ return 0;
+ }
+
+ int index = (int) (mCurrentY / this.mIndexItemHeight);
+ if (index >= this.mIndexItems.length) {
+ index = this.mIndexItems.length - 1;
+ }
+ return index;
+ }
+
+ private float dp2px(int dp) {
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, this.mDisplayMetrics);
+ }
+
+ private float sp2px(int sp) {
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, this.mDisplayMetrics);
+ }
+
+ public void setIndexItems(String... indexItems) {
+ mIndexItems = Arrays.copyOf(indexItems, indexItems.length);
+ requestLayout();
+ }
+
+ public void setTextColor(int color) {
+ mTextColor = color;
+ mPaint.setColor(color);
+ invalidate();
+ }
+
+ public void setPosition(int position) {
+ if (position != POSITION_RIGHT && position != POSITION_LEFT) {
+ throw new IllegalArgumentException("the position must be POSITION_RIGHT or POSITION_LEFT");
+ }
+
+ mSideBarPosition = position;
+ requestLayout();
+ }
+
+ public void setMaxOffset(int offset) {
+ mMaxOffset = offset;
+ invalidate();
+ }
+
+ public void setLazyRespond(boolean lazyRespond) {
+ mLazyRespond = lazyRespond;
+ }
+
+ public void setTextAlign(int align) {
+ if (mTextAlignment == align) {
+ return;
+ }
+ switch (align) {
+ case TEXT_ALIGN_CENTER:
+ mPaint.setTextAlign(Paint.Align.CENTER);
+ break;
+ case TEXT_ALIGN_LEFT:
+ mPaint.setTextAlign(Paint.Align.LEFT);
+ break;
+ case TEXT_ALIGN_RIGHT:
+ mPaint.setTextAlign(Paint.Align.RIGHT);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "the alignment must be TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT or TEXT_ALIGN_RIGHT");
+ }
+ mTextAlignment = align;
+ invalidate();
+ }
+
+ public void setTextSize(float size) {
+ if (mTextSize == size) {
+ return;
+ }
+ mTextSize = size;
+ mPaint.setTextSize(size);
+ invalidate();
+ }
+
+ public void setOnSelectIndexItemListener(OnSelectIndexItemListener onSelectIndexItemListener) {
+ this.onSelectIndexItemListener = onSelectIndexItemListener;
+ }
+
+ public interface OnSelectIndexItemListener {
+ void onSelectIndexItem(String index);
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/WaveView.java b/widget/src/main/java/com/hzecool/widget/WaveView.java
new file mode 100644
index 0000000..60366cf
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/WaveView.java
@@ -0,0 +1,166 @@
+package com.hzecool.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * 水波纹特效
+ * Created by fbchen2 on 2016/5/25.
+ */
+public class WaveView extends View {
+ private float mInitialRadius; // 初始波纹半径
+ private float mMaxRadius; // 最大波纹半径
+ private long mDuration = 2000; // 一个波纹从创建到消失的持续时间
+ private int mSpeed = 500; // 波纹的创建速度,每500ms创建一个
+ private float mMaxRadiusRate = 0.85f;
+ private boolean mMaxRadiusSet;
+ private boolean mIsRunning;
+ private long mLastCreateTime;
+ private List mCircleList = new ArrayList();
+
+ private Runnable mCreateCircle = new Runnable() {
+ @Override
+ public void run() {
+ if (mIsRunning) {
+ newCircle();
+ postDelayed(mCreateCircle, mSpeed);
+ }
+ }
+ };
+
+ private Interpolator mInterpolator = new LinearInterpolator();
+
+ private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+ public WaveView(Context context) {
+ super(context);
+ }
+
+ public WaveView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setStyle(Paint.Style style) {
+ mPaint.setStyle(style);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ if (!mMaxRadiusSet) {
+ mMaxRadius = Math.min(w, h) * mMaxRadiusRate / 2.0f;
+ }
+ }
+
+ public void setMaxRadiusRate(float maxRadiusRate) {
+ mMaxRadiusRate = maxRadiusRate;
+ }
+
+ public void setColor(int color) {
+ mPaint.setColor(color);
+ }
+
+ /**
+ * 开始
+ */
+ public void start() {
+ if (!mIsRunning) {
+ mIsRunning = true;
+ mCreateCircle.run();
+ }
+ }
+
+ /**
+ * 缓慢停止
+ */
+ public void stop() {
+ mIsRunning = false;
+ }
+
+ /**
+ * 立即停止
+ */
+ public void stopImmediately() {
+ mIsRunning = false;
+ mCircleList.clear();
+ invalidate();
+ }
+
+ protected void onDraw(Canvas canvas) {
+ Iterator iterator = mCircleList.iterator();
+ while (iterator.hasNext()) {
+ Circle circle = iterator.next();
+ float radius = circle.getCurrentRadius();
+ if (System.currentTimeMillis() - circle.mCreateTime < mDuration) {
+ mPaint.setAlpha(circle.getAlpha());
+ canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mPaint);
+ } else {
+ iterator.remove();
+ }
+ }
+ if (mCircleList.size() > 0) {
+ postInvalidateDelayed(10);
+ }
+ }
+
+ public void setInitialRadius(float radius) {
+ mInitialRadius = radius;
+ }
+
+ public void setDuration(long duration) {
+ mDuration = duration;
+ }
+
+ public void setMaxRadius(float maxRadius) {
+ mMaxRadius = maxRadius;
+ mMaxRadiusSet = true;
+ }
+
+ public void setSpeed(int speed) {
+ mSpeed = speed;
+ }
+
+ private void newCircle() {
+ long currentTime = System.currentTimeMillis();
+ if (currentTime - mLastCreateTime < mSpeed) {
+ return;
+ }
+ Circle circle = new Circle();
+ mCircleList.add(circle);
+ invalidate();
+ mLastCreateTime = currentTime;
+ }
+
+ private class Circle {
+ private long mCreateTime;
+
+ Circle() {
+ mCreateTime = System.currentTimeMillis();
+ }
+
+ int getAlpha() {
+ float percent = (getCurrentRadius() - mInitialRadius) / (mMaxRadius - mInitialRadius);
+ return (int) (255 - mInterpolator.getInterpolation(percent) * 255);
+ }
+
+ float getCurrentRadius() {
+ float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;
+ return mInitialRadius + mInterpolator.getInterpolation(percent) * (mMaxRadius - mInitialRadius);
+ }
+ }
+
+ public void setInterpolator(Interpolator interpolator) {
+ mInterpolator = interpolator;
+ if (mInterpolator == null) {
+ mInterpolator = new LinearInterpolator();
+ }
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/badgeview/BadgeView.java b/widget/src/main/java/com/hzecool/widget/badgeview/BadgeView.java
new file mode 100644
index 0000000..3fe637c
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/badgeview/BadgeView.java
@@ -0,0 +1,221 @@
+package com.hzecool.widget.badgeview;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RoundRectShape;
+import android.support.v7.widget.AppCompatTextView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.TabWidget;
+
+/**
+ * 自定义角标控件
+ * Created by wangzg on 2017/4/12.
+ */
+
+public class BadgeView extends AppCompatTextView {
+
+ private boolean mHideOnNull = true;
+
+ public BadgeView(Context context) {
+ this(context, null);
+ }
+
+ public BadgeView(Context context, AttributeSet attrs) {
+ this(context, attrs, android.R.attr.textViewStyle);
+ }
+
+ public BadgeView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ init();
+ }
+
+ private void init() {
+ if (!(getLayoutParams() instanceof LayoutParams)) {
+ LayoutParams layoutParams =
+ new LayoutParams(
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
+ Gravity.RIGHT | Gravity.TOP);
+ setLayoutParams(layoutParams);
+ }
+
+ // set default font
+ setTextColor(Color.WHITE);
+ setTypeface(Typeface.DEFAULT_BOLD);
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, 11);
+ setPadding(dip2Px(5), dip2Px(1), dip2Px(5), dip2Px(1));
+
+ // set default background
+ setBackground(9, Color.parseColor("#d3321b"));
+
+ setGravity(Gravity.CENTER);
+
+ // default values
+ setHideOnNull(true);
+ setBadgeCount("0");
+ }
+
+ public void setBackground(int dipRadius, int badgeColor) {
+ int radius = dip2Px(dipRadius);
+ float[] radiusArray = new float[]{radius, radius, radius, radius, radius, radius, radius, radius};
+
+ RoundRectShape roundRect = new RoundRectShape(radiusArray, null, null);
+ ShapeDrawable bgDrawable = new ShapeDrawable(roundRect);
+ bgDrawable.getPaint().setColor(badgeColor);
+ setBackground(bgDrawable);
+ }
+
+ /**
+ * @return Returns true if view is hidden on badge value 0 or null;
+ */
+ public boolean isHideOnNull() {
+ return mHideOnNull;
+ }
+
+ /**
+ * @param hideOnNull the hideOnNull to set
+ */
+ public void setHideOnNull(boolean hideOnNull) {
+ mHideOnNull = hideOnNull;
+ setText(getText());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.widget.TextView#setText(java.lang.CharSequence, android.widget.TextView.BufferType)
+ */
+ @Override
+ public void setText(CharSequence text, BufferType type) {
+ if (isHideOnNull() && (text == null || text.toString().equalsIgnoreCase("0")) || text.toString().equalsIgnoreCase("0.0")) {
+ setVisibility(View.GONE);
+ } else {
+ setVisibility(View.VISIBLE);
+ }
+ super.setText(text, type);
+ }
+
+ public void setBadgeCount(String count) {
+ setText(String.valueOf(count));
+ }
+
+ public Integer getBadgeCount() {
+ if (getText() == null) {
+ return null;
+ }
+
+ String text = getText().toString();
+ try {
+ return Integer.parseInt(text);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ public void setBadgeGravity(int gravity) {
+ FrameLayout.LayoutParams params = (LayoutParams) getLayoutParams();
+ params.gravity = gravity;
+ setLayoutParams(params);
+ }
+
+ public int getBadgeGravity() {
+ FrameLayout.LayoutParams params = (LayoutParams) getLayoutParams();
+ return params.gravity;
+ }
+
+ public void setBadgeMargin(int dipMargin) {
+ setBadgeMargin(dipMargin, dipMargin, dipMargin, dipMargin);
+ }
+
+ public void setBadgeMargin(int leftDipMargin, int topDipMargin, int rightDipMargin, int bottomDipMargin) {
+ FrameLayout.LayoutParams params = (LayoutParams) getLayoutParams();
+ params.leftMargin = dip2Px(leftDipMargin);
+ params.topMargin = dip2Px(topDipMargin);
+ params.rightMargin = dip2Px(rightDipMargin);
+ params.bottomMargin = dip2Px(bottomDipMargin);
+ setLayoutParams(params);
+ }
+
+ public int[] getBadgeMargin() {
+ FrameLayout.LayoutParams params = (LayoutParams) getLayoutParams();
+ return new int[]{params.leftMargin, params.topMargin, params.rightMargin, params.bottomMargin};
+ }
+
+ public void incrementBadgeCount(String increment) {
+ Integer count = getBadgeCount();
+ if (count == null) {
+ setBadgeCount(increment);
+ } else {
+ setBadgeCount(increment + count);
+ }
+ }
+
+ /*
+ * Attach the BadgeView to the TabWidget
+ *
+ * @param target the TabWidget to attach the BadgeView
+ *
+ * @param tabIndex index of the tab
+ */
+ public void setTargetView(TabWidget target, int tabIndex) {
+ View tabView = target.getChildTabViewAt(tabIndex);
+ setTargetView(tabView);
+ }
+
+ /*
+ * Attach the BadgeView to the target view
+ *
+ * @param target the view to attach the BadgeView
+ */
+ public void setTargetView(View target) {
+ if (getParent() != null) {
+ ((ViewGroup) getParent()).removeView(this);
+ }
+
+ if (target == null) {
+ return;
+ }
+
+ if (target.getParent() instanceof FrameLayout) {
+ ((FrameLayout) target.getParent()).addView(this);
+
+ } else if (target.getParent() instanceof ViewGroup) {
+ // use a new Framelayout container for adding badge
+ ViewGroup parentContainer = (ViewGroup) target.getParent();
+ int groupIndex = parentContainer.indexOfChild(target);
+ parentContainer.removeView(target);
+
+ FrameLayout badgeContainer = new FrameLayout(getContext());
+ ViewGroup.LayoutParams parentLayoutParams = target.getLayoutParams();
+
+ badgeContainer.setLayoutParams(parentLayoutParams);
+ target.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+
+ parentContainer.addView(badgeContainer, groupIndex, parentLayoutParams);
+ badgeContainer.addView(target);
+
+ badgeContainer.addView(this);
+ } else if (target.getParent() == null) {
+ Log.e(getClass().getSimpleName(), "ParentView is needed");
+ }
+
+ }
+
+ /*
+ * converts dip to px
+ */
+ private int dip2Px(float dip) {
+ return (int) (dip * getContext().getResources().getDisplayMetrics().density + 0.5f);
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/circleimageview/CircleImageView.java b/widget/src/main/java/com/hzecool/widget/circleimageview/CircleImageView.java
new file mode 100644
index 0000000..f697ef3
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/circleimageview/CircleImageView.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright 2014 - 2017 Henning Dodenhof
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.hzecool.widget.circleimageview;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.support.annotation.ColorInt;
+import android.support.annotation.ColorRes;
+import android.support.annotation.DrawableRes;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+import com.hzecool.widget.R;
+
+public class CircleImageView extends android.support.v7.widget.AppCompatImageView {
+
+ private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
+
+ private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
+ private static final int COLORDRAWABLE_DIMENSION = 2;
+
+ private static final int DEFAULT_BORDER_WIDTH = 0;
+ private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
+ private static final int DEFAULT_FILL_COLOR = Color.TRANSPARENT;
+ private static final boolean DEFAULT_BORDER_OVERLAY = false;
+
+ private final RectF mDrawableRect = new RectF();
+ private final RectF mBorderRect = new RectF();
+
+ private final Matrix mShaderMatrix = new Matrix();
+ private final Paint mBitmapPaint = new Paint();
+ private final Paint mBorderPaint = new Paint();
+ private final Paint mFillPaint = new Paint();
+
+ private int mBorderColor = DEFAULT_BORDER_COLOR;
+ private int mBorderWidth = DEFAULT_BORDER_WIDTH;
+ private int mFillColor = DEFAULT_FILL_COLOR;
+
+ private Bitmap mBitmap;
+ private BitmapShader mBitmapShader;
+ private int mBitmapWidth;
+ private int mBitmapHeight;
+
+ private float mDrawableRadius;
+ private float mBorderRadius;
+
+ private ColorFilter mColorFilter;
+
+ private boolean mReady;
+ private boolean mSetupPending;
+ private boolean mBorderOverlay;
+ private boolean mDisableCircularTransformation;
+
+ public CircleImageView(Context context) {
+ super(context);
+
+ init();
+ }
+
+ public CircleImageView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
+
+ mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH);
+ mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR);
+ mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY);
+ mFillColor = a.getColor(R.styleable.CircleImageView_civ_fill_color, DEFAULT_FILL_COLOR);
+
+ a.recycle();
+
+ init();
+ }
+
+ private void init() {
+ super.setScaleType(SCALE_TYPE);
+ mReady = true;
+
+ if (mSetupPending) {
+ setup();
+ mSetupPending = false;
+ }
+ }
+
+ @Override
+ public ScaleType getScaleType() {
+ return SCALE_TYPE;
+ }
+
+ @Override
+ public void setScaleType(ScaleType scaleType) {
+ if (scaleType != SCALE_TYPE) {
+ throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
+ }
+ }
+
+ @Override
+ public void setAdjustViewBounds(boolean adjustViewBounds) {
+ if (adjustViewBounds) {
+ throw new IllegalArgumentException("adjustViewBounds not supported.");
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mDisableCircularTransformation) {
+ super.onDraw(canvas);
+ return;
+ }
+
+ if (mBitmap == null) {
+ return;
+ }
+
+ if (mFillColor != Color.TRANSPARENT) {
+ canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mFillPaint);
+ }
+ canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint);
+ if (mBorderWidth > 0) {
+ canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint);
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ setup();
+ }
+
+ @Override
+ public void setPadding(int left, int top, int right, int bottom) {
+ super.setPadding(left, top, right, bottom);
+ setup();
+ }
+
+ @Override
+ public void setPaddingRelative(int start, int top, int end, int bottom) {
+ super.setPaddingRelative(start, top, end, bottom);
+ setup();
+ }
+
+ public int getBorderColor() {
+ return mBorderColor;
+ }
+
+ public void setBorderColor(@ColorInt int borderColor) {
+ if (borderColor == mBorderColor) {
+ return;
+ }
+
+ mBorderColor = borderColor;
+ mBorderPaint.setColor(mBorderColor);
+ invalidate();
+ }
+
+ /**
+ * @deprecated Use {@link #setBorderColor(int)} instead
+ */
+ @Deprecated
+ public void setBorderColorResource(@ColorRes int borderColorRes) {
+ setBorderColor(getContext().getResources().getColor(borderColorRes));
+ }
+
+ /**
+ * Return the color drawn behind the circle-shaped drawable.
+ *
+ * @return The color drawn behind the drawable
+ *
+ * @deprecated Fill color support is going to be removed in the future
+ */
+ @Deprecated
+ public int getFillColor() {
+ return mFillColor;
+ }
+
+ /**
+ * Set a color to be drawn behind the circle-shaped drawable. Note that
+ * this has no effect if the drawable is opaque or no drawable is set.
+ *
+ * @param fillColor The color to be drawn behind the drawable
+ *
+ * @deprecated Fill color support is going to be removed in the future
+ */
+ @Deprecated
+ public void setFillColor(@ColorInt int fillColor) {
+ if (fillColor == mFillColor) {
+ return;
+ }
+
+ mFillColor = fillColor;
+ mFillPaint.setColor(fillColor);
+ invalidate();
+ }
+
+ /**
+ * Set a color to be drawn behind the circle-shaped drawable. Note that
+ * this has no effect if the drawable is opaque or no drawable is set.
+ *
+ * @param fillColorRes The color resource to be resolved to a color and
+ * drawn behind the drawable
+ *
+ * @deprecated Fill color support is going to be removed in the future
+ */
+ @Deprecated
+ public void setFillColorResource(@ColorRes int fillColorRes) {
+ setFillColor(getContext().getResources().getColor(fillColorRes));
+ }
+
+ public int getBorderWidth() {
+ return mBorderWidth;
+ }
+
+ public void setBorderWidth(int borderWidth) {
+ if (borderWidth == mBorderWidth) {
+ return;
+ }
+
+ mBorderWidth = borderWidth;
+ setup();
+ }
+
+ public boolean isBorderOverlay() {
+ return mBorderOverlay;
+ }
+
+ public void setBorderOverlay(boolean borderOverlay) {
+ if (borderOverlay == mBorderOverlay) {
+ return;
+ }
+
+ mBorderOverlay = borderOverlay;
+ setup();
+ }
+
+ public boolean isDisableCircularTransformation() {
+ return mDisableCircularTransformation;
+ }
+
+ public void setDisableCircularTransformation(boolean disableCircularTransformation) {
+ if (mDisableCircularTransformation == disableCircularTransformation) {
+ return;
+ }
+
+ mDisableCircularTransformation = disableCircularTransformation;
+ initializeBitmap();
+ }
+
+ @Override
+ public void setImageBitmap(Bitmap bm) {
+ super.setImageBitmap(bm);
+ initializeBitmap();
+ }
+
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ super.setImageDrawable(drawable);
+ initializeBitmap();
+ }
+
+ @Override
+ public void setImageResource(@DrawableRes int resId) {
+ super.setImageResource(resId);
+ initializeBitmap();
+ }
+
+ @Override
+ public void setImageURI(Uri uri) {
+ super.setImageURI(uri);
+ initializeBitmap();
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ if (cf == mColorFilter) {
+ return;
+ }
+
+ mColorFilter = cf;
+ applyColorFilter();
+ invalidate();
+ }
+
+ @Override
+ public ColorFilter getColorFilter() {
+ return mColorFilter;
+ }
+
+ private void applyColorFilter() {
+ if (mBitmapPaint != null) {
+ mBitmapPaint.setColorFilter(mColorFilter);
+ }
+ }
+
+ private Bitmap getBitmapFromDrawable(Drawable drawable) {
+ if (drawable == null) {
+ return null;
+ }
+
+ if (drawable instanceof BitmapDrawable) {
+ return ((BitmapDrawable) drawable).getBitmap();
+ }
+
+ try {
+ Bitmap bitmap;
+
+ if (drawable instanceof ColorDrawable) {
+ bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
+ } else {
+ bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
+ }
+
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bitmap;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private void initializeBitmap() {
+ if (mDisableCircularTransformation) {
+ mBitmap = null;
+ } else {
+ mBitmap = getBitmapFromDrawable(getDrawable());
+ }
+ setup();
+ }
+
+ private void setup() {
+ if (!mReady) {
+ mSetupPending = true;
+ return;
+ }
+
+ if (getWidth() == 0 && getHeight() == 0) {
+ return;
+ }
+
+ if (mBitmap == null) {
+ invalidate();
+ return;
+ }
+
+ mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+
+ mBitmapPaint.setAntiAlias(true);
+ mBitmapPaint.setShader(mBitmapShader);
+
+ mBorderPaint.setStyle(Paint.Style.STROKE);
+ mBorderPaint.setAntiAlias(true);
+ mBorderPaint.setColor(mBorderColor);
+ mBorderPaint.setStrokeWidth(mBorderWidth);
+
+ mFillPaint.setStyle(Paint.Style.FILL);
+ mFillPaint.setAntiAlias(true);
+ mFillPaint.setColor(mFillColor);
+
+ mBitmapHeight = mBitmap.getHeight();
+ mBitmapWidth = mBitmap.getWidth();
+
+ mBorderRect.set(calculateBounds());
+ mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);
+
+ mDrawableRect.set(mBorderRect);
+ if (!mBorderOverlay && mBorderWidth > 0) {
+ mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f);
+ }
+ mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);
+
+ applyColorFilter();
+ updateShaderMatrix();
+ invalidate();
+ }
+
+ private RectF calculateBounds() {
+ int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
+ int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom();
+
+ int sideLength = Math.min(availableWidth, availableHeight);
+
+ float left = getPaddingLeft() + (availableWidth - sideLength) / 2f;
+ float top = getPaddingTop() + (availableHeight - sideLength) / 2f;
+
+ return new RectF(left, top, left + sideLength, top + sideLength);
+ }
+
+ private void updateShaderMatrix() {
+ float scale;
+ float dx = 0;
+ float dy = 0;
+
+ mShaderMatrix.set(null);
+
+ if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
+ scale = mDrawableRect.height() / (float) mBitmapHeight;
+ dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
+ } else {
+ scale = mDrawableRect.width() / (float) mBitmapWidth;
+ dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
+ }
+
+ mShaderMatrix.setScale(scale, scale);
+ mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);
+
+ mBitmapShader.setLocalMatrix(mShaderMatrix);
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/colorpicker/ColorPickerView.java b/widget/src/main/java/com/hzecool/widget/colorpicker/ColorPickerView.java
new file mode 100644
index 0000000..90ebca9
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/colorpicker/ColorPickerView.java
@@ -0,0 +1,187 @@
+package com.hzecool.widget.colorpicker;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.ImageView;
+
+import com.hzecool.widget.R;
+
+public class ColorPickerView extends ImageView {
+ Context context;
+ private Bitmap iconBitMap;
+ float iconRadius;// 吸管圆的半径
+ float iconCenterX;
+ float iconCenterY;
+ PointF iconPoint;// 点击位置坐标
+
+ public ColorPickerView(Context context) {
+ this(context, null);
+ }
+
+ public ColorPickerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ this.context = context;
+ init();
+ }
+
+ public ColorPickerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ init();
+ }
+
+ Paint mBitmapPaint;
+ Bitmap imageBitmap;
+ float viewRadius;// 整个view半径
+ float radius;// 图片半径
+
+ /**
+ * 初始化画笔
+ */
+ private void init() {
+ iconBitMap = BitmapFactory.decodeResource(context.getResources(),
+ R.drawable.pickup);// 吸管的图片
+ iconRadius = iconBitMap.getWidth() / 2;// 吸管的图片一半
+
+ mBitmapPaint = new Paint();
+ iconPoint = new PointF();
+
+ imageBitmap = ((BitmapDrawable) getDrawable()).getBitmap();
+ radius = imageBitmap.getHeight() / 2;// 图片半径,180
+
+ // // 初始化
+ iconPoint.x = radius;
+ iconPoint.y = radius;
+
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // TODO Auto-generated method stub
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ Canvas canvas;
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ // TODO Auto-generated method stub
+ super.onDraw(canvas);
+ this.canvas = canvas;
+
+ viewRadius = this.getWidth() / 2;// 整个view半径
+
+ canvas.drawBitmap(iconBitMap, iconPoint.x - iconRadius, iconPoint.y
+ - iconRadius, mBitmapPaint);
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ int pixel;
+ int r;
+ int g;
+ int b;
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_MOVE:
+ proofLeft(x, y);
+ pixel = getImagePixel(iconPoint.x, iconPoint.y);
+ r = Color.red(pixel);
+ g = Color.green(pixel);
+ b = Color.blue(pixel);
+ if (mChangedListener != null) {
+ mChangedListener.onMoveColor(r, g, b);
+ }
+ if (isMove) {
+ isMove = !isMove;
+ invalidate();
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ pixel = getImagePixel(iconPoint.x, iconPoint.y);
+ r = Color.red(pixel);
+ g = Color.green(pixel);
+ b = Color.blue(pixel);
+ if (mChangedListener != null) {
+ mChangedListener.onColorChanged(r, g, b);
+ }
+ break;
+
+ default:
+ break;
+ }
+ return true;
+ }
+
+ public int getImagePixel(float x, float y) {
+
+ Bitmap bitmap = imageBitmap;
+ // 为了防止越界
+ int intX = (int) x;
+ int intY = (int) y;
+ if (intX < 0)
+ intX = 0;
+ if (intY < 0)
+ intY = 0;
+ if (intX >= bitmap.getWidth()) {
+ intX = bitmap.getWidth() - 1;
+ }
+ if (intY >= bitmap.getHeight()) {
+ intY = bitmap.getHeight() - 1;
+ }
+ int pixel = bitmap.getPixel(intX, intY);
+ return pixel;
+
+ }
+
+ /**
+ * R = sqrt(x * x + y * y)
+ * point.x = x * r / R + r
+ * point.y = y * r / R + r
+ */
+ private void proofLeft(float x, float y) {
+
+ float h = x - viewRadius; // 取xy点和圆点 的三角形宽
+ float w = y - viewRadius;// 取xy点和圆点 的三角形长
+ float h2 = h * h;
+ float w2 = w * w;
+ float distance = (float) Math.sqrt((h2 + w2)); // 勾股定理求 斜边距离
+ if (distance > radius) { // 如果斜边距离大于半径,则取点和圆最近的一个点为x,y
+ float maxX = x - viewRadius;
+ float maxY = y - viewRadius;
+ x = ((radius * maxX) / distance) + viewRadius; // 通过三角形一边平行原理求出x,y
+ y = ((radius * maxY) / distance) + viewRadius;
+ }
+ iconPoint.x = x;
+ iconPoint.y = y;
+
+ isMove = true;
+ }
+
+ boolean isMove;
+
+ public void setOnColorChangedListenner(OnColorChangedListener l) {
+ this.mChangedListener = l;
+ }
+
+ private OnColorChangedListener mChangedListener;
+
+ // 内部接口 回调颜色 rgb值
+ public interface OnColorChangedListener {
+ // 手指抬起,确定颜色回调
+ void onColorChanged(int r, int g, int b);
+
+ // 移动时颜色回调
+ void onMoveColor(int r, int g, int b);
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/imgselector/MultiImageSelector.java b/widget/src/main/java/com/hzecool/widget/imgselector/MultiImageSelector.java
new file mode 100644
index 0000000..ab0a1a9
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/imgselector/MultiImageSelector.java
@@ -0,0 +1,117 @@
+package com.hzecool.widget.imgselector;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.ContextCompat;
+import android.widget.Toast;
+
+import com.hzecool.widget.R;
+
+import java.util.ArrayList;
+
+/**
+ * 图片选择器
+ * Created by nereo on 16/3/17.
+ */
+public class MultiImageSelector {
+
+ public static final String EXTRA_RESULT = MultiImageSelectorActivity.EXTRA_RESULT;
+
+ private boolean mShowCamera = true;
+ private int mMaxCount = 9;
+ private int mMode = MultiImageSelectorActivity.MODE_MULTI;
+ private ArrayList mOriginData;
+ private static MultiImageSelector sSelector;
+
+ @Deprecated
+ private MultiImageSelector(Context context){
+
+ }
+
+ private MultiImageSelector(){}
+
+ @Deprecated
+ public static MultiImageSelector create(Context context){
+ if(sSelector == null){
+ sSelector = new MultiImageSelector(context);
+ }
+ return sSelector;
+ }
+
+ public static MultiImageSelector create(){
+ if(sSelector == null){
+ sSelector = new MultiImageSelector();
+ }
+ return sSelector;
+ }
+
+ public MultiImageSelector showCamera(boolean show){
+ mShowCamera = show;
+ return sSelector;
+ }
+
+ public MultiImageSelector count(int count){
+ mMaxCount = count;
+ return sSelector;
+ }
+
+ public MultiImageSelector single(){
+ mMode = MultiImageSelectorActivity.MODE_SINGLE;
+ return sSelector;
+ }
+
+ public MultiImageSelector multi(){
+ mMode = MultiImageSelectorActivity.MODE_MULTI;
+ return sSelector;
+ }
+
+ public MultiImageSelector origin(ArrayList images){
+ mOriginData = images;
+ return sSelector;
+ }
+
+ public void start(Activity activity, int requestCode){
+ final Context context = activity;
+ if(hasPermission(context)) {
+ activity.startActivityForResult(createIntent(context), requestCode);
+ }else{
+ Toast.makeText(context, R.string.mis_error_no_permission, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ public void start(Fragment fragment, int requestCode){
+ final Context context = fragment.getContext();
+ if(hasPermission(context)) {
+ fragment.startActivityForResult(createIntent(context), requestCode);
+ }else{
+ Toast.makeText(context, R.string.mis_error_no_permission, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private boolean hasPermission(Context context){
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN){
+ // Permission was added in API Level 16
+ return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+ return true;
+ }
+
+ private Intent createIntent(Context context){
+
+ Intent intent = new Intent(context, MultiImageSelectorActivity.class);
+ intent.putExtra(MultiImageSelectorActivity.EXTRA_SHOW_CAMERA, mShowCamera);
+ intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_COUNT, mMaxCount);
+ if(mOriginData != null){
+ intent.putStringArrayListExtra(MultiImageSelectorActivity.EXTRA_DEFAULT_SELECTED_LIST, mOriginData);
+ }
+ intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_MODE, mMode);
+
+ return intent;
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/imgselector/MultiImageSelectorActivity.java b/widget/src/main/java/com/hzecool/widget/imgselector/MultiImageSelectorActivity.java
new file mode 100644
index 0000000..e52f714
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/imgselector/MultiImageSelectorActivity.java
@@ -0,0 +1,182 @@
+package com.hzecool.widget.imgselector;
+
+import android.content.Intent;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+
+import com.hzecool.widget.R;
+
+import java.io.File;
+import java.util.ArrayList;
+
+/**
+ * Multi image selector
+ * Created by Nereo on 2015/4/7.
+ * Updated by nereo on 2016/1/19.
+ * Updated by nereo on 2016/5/18.
+ */
+public class MultiImageSelectorActivity extends AppCompatActivity
+ implements MultiImageSelectorFragment.Callback{
+
+ // Single choice
+ public static final int MODE_SINGLE = 0;
+ // Multi choice
+ public static final int MODE_MULTI = 1;
+
+ /** Max image size,int,{@link #DEFAULT_IMAGE_SIZE} by default */
+ public static final String EXTRA_SELECT_COUNT = "max_select_count";
+ /** Select mode,{@link #MODE_MULTI} by default */
+ public static final String EXTRA_SELECT_MODE = "select_count_mode";
+ /** Whether show camera,true by default */
+ public static final String EXTRA_SHOW_CAMERA = "show_camera";
+ /** Result data set,ArrayList<String>*/
+ public static final String EXTRA_RESULT = "select_result";
+ /** Original data set */
+ public static final String EXTRA_DEFAULT_SELECTED_LIST = "default_list";
+ // Default image size
+ private static final int DEFAULT_IMAGE_SIZE = 9;
+
+ private ArrayList resultList = new ArrayList<>();
+ private Button mSubmitButton;
+ private int mDefaultCount = DEFAULT_IMAGE_SIZE;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTheme(R.style.MIS_NO_ACTIONBAR);
+ setContentView(R.layout.mis_activity_default);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ getWindow().setStatusBarColor(Color.BLACK);
+ }
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ if(toolbar != null){
+ setSupportActionBar(toolbar);
+ }
+
+ final ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ final Intent intent = getIntent();
+ mDefaultCount = intent.getIntExtra(EXTRA_SELECT_COUNT, DEFAULT_IMAGE_SIZE);
+ final int mode = intent.getIntExtra(EXTRA_SELECT_MODE, MODE_MULTI);
+ final boolean isShow = intent.getBooleanExtra(EXTRA_SHOW_CAMERA, true);
+ if(mode == MODE_MULTI && intent.hasExtra(EXTRA_DEFAULT_SELECTED_LIST)) {
+ resultList = intent.getStringArrayListExtra(EXTRA_DEFAULT_SELECTED_LIST);
+ }
+
+ mSubmitButton = (Button) findViewById(R.id.commit);
+ if(mode == MODE_MULTI){
+ updateDoneText(resultList);
+ mSubmitButton.setVisibility(View.VISIBLE);
+ mSubmitButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if(resultList != null && resultList.size() >0){
+ // Notify success
+ Intent data = new Intent();
+ data.putStringArrayListExtra(EXTRA_RESULT, resultList);
+ setResult(RESULT_OK, data);
+ }else{
+ setResult(RESULT_CANCELED);
+ }
+ finish();
+ }
+ });
+ }else{
+ mSubmitButton.setVisibility(View.GONE);
+ }
+
+ if(savedInstanceState == null){
+ Bundle bundle = new Bundle();
+ bundle.putInt(MultiImageSelectorFragment.EXTRA_SELECT_COUNT, mDefaultCount);
+ bundle.putInt(MultiImageSelectorFragment.EXTRA_SELECT_MODE, mode);
+ bundle.putBoolean(MultiImageSelectorFragment.EXTRA_SHOW_CAMERA, isShow);
+ bundle.putStringArrayList(MultiImageSelectorFragment.EXTRA_DEFAULT_SELECTED_LIST, resultList);
+
+ getSupportFragmentManager().beginTransaction()
+ .add(R.id.image_grid, Fragment.instantiate(this, MultiImageSelectorFragment.class.getName(), bundle))
+ .commit();
+ }
+
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ setResult(RESULT_CANCELED);
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * Update done button by select image data
+ * @param resultList selected image data
+ */
+ private void updateDoneText(ArrayList resultList){
+ int size = 0;
+ if(resultList == null || resultList.size()<=0){
+ mSubmitButton.setText(R.string.mis_action_done);
+ mSubmitButton.setEnabled(false);
+ }else{
+ size = resultList.size();
+ mSubmitButton.setEnabled(true);
+ }
+ mSubmitButton.setText(getString(R.string.mis_action_button_string,
+ getString(R.string.mis_action_done), size, mDefaultCount));
+ }
+
+ @Override
+ public void onSingleImageSelected(String path) {
+ Intent data = new Intent();
+ resultList.add(path);
+ data.putStringArrayListExtra(EXTRA_RESULT, resultList);
+ setResult(RESULT_OK, data);
+ finish();
+ }
+
+ @Override
+ public void onImageSelected(String path) {
+ if(!resultList.contains(path)) {
+ resultList.add(path);
+ }
+ updateDoneText(resultList);
+ }
+
+ @Override
+ public void onImageUnselected(String path) {
+ if(resultList.contains(path)){
+ resultList.remove(path);
+ }
+ updateDoneText(resultList);
+ }
+
+ @Override
+ public void onCameraShot(File imageFile) {
+ if(imageFile != null) {
+ // notify system the image has change
+ sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(imageFile)));
+
+ Intent data = new Intent();
+ resultList.add(imageFile.getAbsolutePath());
+ data.putStringArrayListExtra(EXTRA_RESULT, resultList);
+ setResult(RESULT_OK, data);
+ finish();
+ }
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/imgselector/MultiImageSelectorFragment.java b/widget/src/main/java/com/hzecool/widget/imgselector/MultiImageSelectorFragment.java
new file mode 100644
index 0000000..d1b2381
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/imgselector/MultiImageSelectorFragment.java
@@ -0,0 +1,567 @@
+package com.hzecool.widget.imgselector;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.database.Cursor;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.MediaStore;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.widget.ListPopupWindow;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.GridView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.hzecool.widget.R;
+import com.hzecool.widget.imgselector.adapter.FolderAdapter;
+import com.hzecool.widget.imgselector.adapter.ImageGridAdapter;
+import com.hzecool.widget.imgselector.bean.Folder;
+import com.hzecool.widget.imgselector.bean.Image;
+import com.hzecool.widget.imgselector.utils.FileUtils;
+import com.hzecool.widget.imgselector.utils.ScreenUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Multi image selector Fragment
+ * Created by Nereo on 2015/4/7.
+ * Updated by nereo on 2016/5/18.
+ */
+public class MultiImageSelectorFragment extends Fragment {
+
+ public static final String TAG = "MultiImageSelectorFragment";
+
+ private static final int REQUEST_STORAGE_WRITE_ACCESS_PERMISSION = 110;
+ private static final int REQUEST_CAMERA = 100;
+
+ private static final String KEY_TEMP_FILE = "key_temp_file";
+
+ // Single choice
+ public static final int MODE_SINGLE = 0;
+ // Multi choice
+ public static final int MODE_MULTI = 1;
+
+ /**
+ * Max image size,int,
+ */
+ public static final String EXTRA_SELECT_COUNT = "max_select_count";
+ /**
+ * Select mode,{@link #MODE_MULTI} by default
+ */
+ public static final String EXTRA_SELECT_MODE = "select_count_mode";
+ /**
+ * Whether show camera,true by default
+ */
+ public static final String EXTRA_SHOW_CAMERA = "show_camera";
+ /**
+ * Original data set
+ */
+ public static final String EXTRA_DEFAULT_SELECTED_LIST = "default_list";
+
+ // loaders
+ private static final int LOADER_ALL = 0;
+ private static final int LOADER_CATEGORY = 1;
+
+ // image result data set
+ private ArrayList resultList = new ArrayList<>();
+ // folder result data set
+ private ArrayList mResultFolder = new ArrayList<>();
+
+ private GridView mGridView;
+ private Callback mCallback;
+
+ private ImageGridAdapter mImageAdapter;
+ private FolderAdapter mFolderAdapter;
+
+ private ListPopupWindow mFolderPopupWindow;
+
+ private TextView mCategoryText;
+ private View mPopupAnchorView;
+
+ private boolean hasFolderGened = false;
+
+ private File mTmpFile;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ try {
+ mCallback = (Callback) getActivity();
+ } catch (ClassCastException e) {
+ throw new ClassCastException("The Activity must implement MultiImageSelectorFragment.Callback interface...");
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.mis_fragment_multi_image, container, false);
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ final int mode = selectMode();
+ if (mode == MODE_MULTI) {
+ ArrayList tmp = getArguments().getStringArrayList(EXTRA_DEFAULT_SELECTED_LIST);
+ if (tmp != null && tmp.size() > 0) {
+ resultList = tmp;
+ }
+ }
+ mImageAdapter = new ImageGridAdapter(getActivity(), showCamera(), 3);
+ mImageAdapter.showSelectIndicator(mode == MODE_MULTI);
+
+ mPopupAnchorView = view.findViewById(R.id.footer);
+
+ mCategoryText = (TextView) view.findViewById(R.id.category_btn);
+ mCategoryText.setText(R.string.mis_folder_all);
+ mCategoryText.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+
+ if (mFolderPopupWindow == null) {
+ createPopupFolderList();
+ }
+
+ if (mFolderPopupWindow.isShowing()) {
+ mFolderPopupWindow.dismiss();
+ } else {
+ mFolderPopupWindow.show();
+ int index = mFolderAdapter.getSelectIndex();
+ index = index == 0 ? index : index - 1;
+ mFolderPopupWindow.getListView().setSelection(index);
+ }
+ }
+ });
+
+ mGridView = (GridView) view.findViewById(R.id.grid);
+ mGridView.setAdapter(mImageAdapter);
+ mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
+ if (mImageAdapter.isShowCamera()) {
+ if (i == 0) {
+ showCameraAction();
+ } else {
+ Image image = (Image) adapterView.getAdapter().getItem(i);
+ selectImageFromGrid(image, mode);
+ }
+ } else {
+ Image image = (Image) adapterView.getAdapter().getItem(i);
+ selectImageFromGrid(image, mode);
+ }
+ }
+ });
+ mGridView.setOnScrollListener(new AbsListView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+// if (scrollState == SCROLL_STATE_FLING) {
+// Glide.with(view.getContext());
+// } else {
+// Glide.with(view.getContext());
+// }
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+
+ }
+ });
+
+ mFolderAdapter = new FolderAdapter(getActivity());
+ }
+
+ /**
+ * Create popup ListView
+ */
+ private void createPopupFolderList() {
+ Point point = ScreenUtils.getScreenSize(getActivity());
+ int width = point.x;
+ int height = (int) (point.y * (4.5f / 8.0f));
+ mFolderPopupWindow = new ListPopupWindow(getActivity());
+ mFolderPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.WHITE));
+ mFolderPopupWindow.setAdapter(mFolderAdapter);
+ mFolderPopupWindow.setContentWidth(width);
+ mFolderPopupWindow.setWidth(width);
+ mFolderPopupWindow.setHeight(height);
+ mFolderPopupWindow.setAnchorView(mPopupAnchorView);
+ mFolderPopupWindow.setModal(true);
+ mFolderPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
+
+ mFolderAdapter.setSelectIndex(i);
+
+ final int index = i;
+ final AdapterView v = adapterView;
+
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mFolderPopupWindow.dismiss();
+
+ if (index == 0) {
+ getActivity().getSupportLoaderManager().restartLoader(LOADER_ALL, null, mLoaderCallback);
+ mCategoryText.setText(R.string.mis_folder_all);
+ if (showCamera()) {
+ mImageAdapter.setShowCamera(true);
+ } else {
+ mImageAdapter.setShowCamera(false);
+ }
+ } else {
+ Folder folder = (Folder) v.getAdapter().getItem(index);
+ if (null != folder) {
+ mImageAdapter.setData(folder.images);
+ mCategoryText.setText(folder.name);
+ if (resultList != null && resultList.size() > 0) {
+ mImageAdapter.setDefaultSelected(resultList);
+ }
+ }
+ mImageAdapter.setShowCamera(false);
+ }
+
+ mGridView.smoothScrollToPosition(0);
+ }
+ }, 100);
+
+ }
+ });
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putSerializable(KEY_TEMP_FILE, mTmpFile);
+ }
+
+ @Override
+ public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
+ super.onViewStateRestored(savedInstanceState);
+ if (savedInstanceState != null) {
+ mTmpFile = (File) savedInstanceState.getSerializable(KEY_TEMP_FILE);
+ }
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ // load image data
+ getActivity().getSupportLoaderManager().initLoader(LOADER_ALL, null, mLoaderCallback);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == REQUEST_CAMERA) {
+ if (resultCode == Activity.RESULT_OK) {
+ if (mTmpFile != null) {
+ if (mCallback != null) {
+ mCallback.onCameraShot(mTmpFile);
+ }
+ }
+ } else {
+ // delete tmp file
+ while (mTmpFile != null && mTmpFile.exists()) {
+ boolean success = mTmpFile.delete();
+ if (success) {
+ mTmpFile = null;
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (mFolderPopupWindow != null) {
+ if (mFolderPopupWindow.isShowing()) {
+ mFolderPopupWindow.dismiss();
+ }
+ }
+ super.onConfigurationChanged(newConfig);
+ }
+
+ /**
+ * Open camera
+ */
+ private void showCameraAction() {
+ if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ getString(R.string.mis_permission_rationale_write_storage),
+ REQUEST_STORAGE_WRITE_ACCESS_PERMISSION);
+ } else {
+ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
+ try {
+ mTmpFile = FileUtils.createTmpFile(getActivity());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ if (mTmpFile != null && mTmpFile.exists()) {
+// intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(mTmpFile));
+// startActivityForResult(intent, REQUEST_CAMERA);
+
+ int currentapiVersion = android.os.Build.VERSION.SDK_INT;
+ if (currentapiVersion<24){
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(mTmpFile));
+ startActivityForResult(intent, REQUEST_CAMERA);
+ }else {
+ ContentValues contentValues = new ContentValues(1);
+ contentValues.put(MediaStore.Images.Media.DATA, mTmpFile.getAbsolutePath());
+ Uri uri = getContext().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,contentValues);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+ startActivityForResult(intent, REQUEST_CAMERA);
+ }
+
+ } else {
+ Toast.makeText(getActivity(), R.string.mis_error_image_not_exist, Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ Toast.makeText(getActivity(), R.string.mis_msg_no_camera, Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+
+ private void doTakePhoto() {
+ try {
+ ContentValues values = new ContentValues(1);
+ values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpg");
+ Uri mCameraTempUri = getActivity().getContentResolver()
+ .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+
+ takePhoto(this, REQUEST_CAMERA, mCameraTempUri);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void takePhoto(Fragment fragment, int token, Uri uri) {
+ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ if (uri != null) {
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+ intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
+ }
+ fragment.startActivityForResult(intent, token);
+ }
+
+ private void requestPermission(final String permission, String rationale, final int requestCode) {
+ if (shouldShowRequestPermissionRationale(permission)) {
+ new AlertDialog.Builder(getContext())
+ .setTitle(R.string.mis_permission_dialog_title)
+ .setMessage(rationale)
+ .setPositiveButton(R.string.mis_permission_dialog_ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ requestPermissions(new String[]{permission}, requestCode);
+ }
+ })
+ .setNegativeButton(R.string.mis_permission_dialog_cancel, null)
+ .create().show();
+ } else {
+ requestPermissions(new String[]{permission}, requestCode);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if (requestCode == REQUEST_STORAGE_WRITE_ACCESS_PERMISSION) {
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ showCameraAction();
+ }
+ } else {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ }
+
+ /**
+ * notify callback
+ *
+ * @param image image data
+ */
+ private void selectImageFromGrid(Image image, int mode) {
+ if (image != null) {
+ if (mode == MODE_MULTI) {
+ if (resultList.contains(image.path)) {
+ resultList.remove(image.path);
+ if (mCallback != null) {
+ mCallback.onImageUnselected(image.path);
+ }
+ } else {
+ if (selectImageCount() == resultList.size()) {
+ Toast.makeText(getActivity(), R.string.mis_msg_amount_limit, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ resultList.add(image.path);
+ if (mCallback != null) {
+ mCallback.onImageSelected(image.path);
+ }
+ }
+ mImageAdapter.select(image);
+ } else if (mode == MODE_SINGLE) {
+ if (mCallback != null) {
+ mCallback.onSingleImageSelected(image.path);
+ }
+ }
+ }
+ }
+
+ private LoaderManager.LoaderCallbacks mLoaderCallback = new LoaderManager.LoaderCallbacks() {
+
+ private final String[] IMAGE_PROJECTION = {
+ MediaStore.Images.Media.DATA,
+ MediaStore.Images.Media.DISPLAY_NAME,
+ MediaStore.Images.Media.DATE_ADDED,
+ MediaStore.Images.Media.MIME_TYPE,
+ MediaStore.Images.Media.SIZE,
+ MediaStore.Images.Media._ID};
+
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ CursorLoader cursorLoader = null;
+ if (id == LOADER_ALL) {
+ cursorLoader = new CursorLoader(getActivity(),
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION,
+ IMAGE_PROJECTION[4] + ">0 AND " + IMAGE_PROJECTION[3] + "=? OR " + IMAGE_PROJECTION[3] + "=? ",
+ new String[]{"image/jpeg", "image/png"}, IMAGE_PROJECTION[2] + " DESC");
+ } else if (id == LOADER_CATEGORY) {
+ cursorLoader = new CursorLoader(getActivity(),
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION,
+ IMAGE_PROJECTION[4] + ">0 AND " + IMAGE_PROJECTION[0] + " like '%" + args.getString("path") + "%'",
+ null, IMAGE_PROJECTION[2] + " DESC");
+ }
+ return cursorLoader;
+ }
+
+ private boolean fileExist(String path) {
+ if (!TextUtils.isEmpty(path)) {
+ return new File(path).exists();
+ }
+ return false;
+ }
+
+ @Override
+ public void onLoadFinished(Loader loader, Cursor data) {
+ if (data != null) {
+ if (data.getCount() > 0) {
+ List images = new ArrayList<>();
+ data.moveToFirst();
+ do {
+ String path = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[0]));
+ String name = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[1]));
+ long dateTime = data.getLong(data.getColumnIndexOrThrow(IMAGE_PROJECTION[2]));
+ if (!fileExist(path)) {
+ continue;
+ }
+ Image image = null;
+ if (!TextUtils.isEmpty(name)) {
+ image = new Image(path, name, dateTime);
+ images.add(image);
+ }
+ if (!hasFolderGened) {
+ // get all folder data
+ File folderFile = new File(path).getParentFile();
+ if (folderFile != null && folderFile.exists()) {
+ String fp = folderFile.getAbsolutePath();
+ Folder f = getFolderByPath(fp);
+ if (f == null) {
+ Folder folder = new Folder();
+ folder.name = folderFile.getName();
+ folder.path = fp;
+ folder.cover = image;
+ List imageList = new ArrayList<>();
+ imageList.add(image);
+ folder.images = imageList;
+ mResultFolder.add(folder);
+ } else {
+ f.images.add(image);
+ }
+ }
+ }
+
+ } while (data.moveToNext());
+
+ mImageAdapter.setData(images);
+ if (resultList != null && resultList.size() > 0) {
+ mImageAdapter.setDefaultSelected(resultList);
+ }
+ if (!hasFolderGened) {
+ mFolderAdapter.setData(mResultFolder);
+ hasFolderGened = true;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader loader) {
+
+ }
+ };
+
+ private Folder getFolderByPath(String path) {
+ if (mResultFolder != null) {
+ for (Folder folder : mResultFolder) {
+ if (TextUtils.equals(folder.path, path)) {
+ return folder;
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean showCamera() {
+ return getArguments() == null || getArguments().getBoolean(EXTRA_SHOW_CAMERA, true);
+ }
+
+ private int selectMode() {
+ return getArguments() == null ? MODE_MULTI : getArguments().getInt(EXTRA_SELECT_MODE);
+ }
+
+ private int selectImageCount() {
+ return getArguments() == null ? 9 : getArguments().getInt(EXTRA_SELECT_COUNT);
+ }
+
+ /**
+ * Callback for host activity
+ */
+ public interface Callback {
+ void onSingleImageSelected(String path);
+
+ void onImageSelected(String path);
+
+ void onImageUnselected(String path);
+
+ void onCameraShot(File imageFile);
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/imgselector/adapter/FolderAdapter.java b/widget/src/main/java/com/hzecool/widget/imgselector/adapter/FolderAdapter.java
new file mode 100644
index 0000000..142e027
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/imgselector/adapter/FolderAdapter.java
@@ -0,0 +1,178 @@
+package com.hzecool.widget.imgselector.adapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.bumptech.glide.Glide;
+import com.hzecool.widget.R;
+import com.hzecool.widget.imgselector.bean.Folder;
+import com.hzecool.widget.utils.GlideSetting;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * 文件夹Adapter
+ * Created by Nereo on 2015/4/7.
+ * Updated by nereo on 2016/1/19.
+ */
+public class FolderAdapter extends BaseAdapter {
+
+ private Context mContext;
+ private LayoutInflater mInflater;
+
+ private List mFolders = new ArrayList<>();
+
+ int mImageSize;
+
+ int lastSelected = 0;
+
+ public FolderAdapter(Context context) {
+ mContext = context;
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mImageSize = mContext.getResources().getDimensionPixelOffset(R.dimen.mis_folder_cover_size);
+ }
+
+ /**
+ * 设置数据集
+ *
+ * @param folders
+ */
+ public void setData(List folders) {
+ if (folders != null && folders.size() > 0) {
+ mFolders = folders;
+ } else {
+ mFolders.clear();
+ }
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ return mFolders.size() + 1;
+ }
+
+ @Override
+ public Folder getItem(int i) {
+ if (i == 0) return null;
+ return mFolders.get(i - 1);
+ }
+
+ @Override
+ public long getItemId(int i) {
+ return i;
+ }
+
+ @Override
+ public View getView(int i, View view, ViewGroup viewGroup) {
+ ViewHolder holder;
+ if (view == null) {
+ view = mInflater.inflate(R.layout.mis_list_item_folder, viewGroup, false);
+ holder = new ViewHolder(view);
+ } else {
+ holder = (ViewHolder) view.getTag();
+ }
+ if (holder != null) {
+ if (i == 0) {
+ holder.name.setText(R.string.mis_folder_all);
+ holder.path.setText("/sdcard");
+ holder.size.setText(String.format("%d%s",
+ getTotalImageSize(), mContext.getResources().getString(R.string.mis_photo_unit)));
+ if (mFolders.size() > 0) {
+ Folder f = mFolders.get(0);
+ if (f != null) {
+
+ Glide.with(mContext)
+ .load(new File(f.cover.path))
+ .apply(GlideSetting.getGlideSetting()
+ .error(R.mipmap.mis_default_error)
+ .override(R.dimen.mis_folder_cover_size, R.dimen.mis_folder_cover_size)
+ .centerCrop()
+ )
+ .into(holder.cover);
+ } else {
+ holder.cover.setImageResource(R.mipmap.mis_default_error);
+ }
+ }
+ } else {
+ holder.bindData(getItem(i));
+ }
+ if (lastSelected == i) {
+ holder.indicator.setVisibility(View.VISIBLE);
+ } else {
+ holder.indicator.setVisibility(View.INVISIBLE);
+ }
+ }
+ return view;
+ }
+
+ private int getTotalImageSize() {
+ int result = 0;
+ if (mFolders != null && mFolders.size() > 0) {
+ for (Folder f : mFolders) {
+ result += f.images.size();
+ }
+ }
+ return result;
+ }
+
+ public void setSelectIndex(int i) {
+ if (lastSelected == i) return;
+
+ lastSelected = i;
+ notifyDataSetChanged();
+ }
+
+ public int getSelectIndex() {
+ return lastSelected;
+ }
+
+ class ViewHolder {
+ ImageView cover;
+ TextView name;
+ TextView path;
+ TextView size;
+ ImageView indicator;
+
+ ViewHolder(View view) {
+ cover = (ImageView) view.findViewById(R.id.cover);
+ name = (TextView) view.findViewById(R.id.name);
+ path = (TextView) view.findViewById(R.id.path);
+ size = (TextView) view.findViewById(R.id.size);
+ indicator = (ImageView) view.findViewById(R.id.indicator);
+ view.setTag(this);
+ }
+
+ void bindData(Folder data) {
+ if (data == null) {
+ return;
+ }
+ name.setText(data.name);
+ path.setText(data.path);
+ if (data.images != null) {
+ size.setText(String.format("%d%s", data.images.size(), mContext.getResources().getString(R.string.mis_photo_unit)));
+ } else {
+ size.setText("*" + mContext.getResources().getString(R.string.mis_photo_unit));
+ }
+ if (data.cover != null) {
+ Glide.with(mContext)
+ .load(new File(data.cover.path))
+ .apply(GlideSetting.getGlideSetting()
+ .placeholder(R.mipmap.mis_default_error)
+ .override(R.dimen.mis_folder_cover_size, R.dimen.mis_folder_cover_size)
+ .centerCrop())
+ .into(cover);
+ } else {
+ cover.setImageResource(R.mipmap.mis_default_error);
+ }
+ }
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/imgselector/adapter/ImageGridAdapter.java b/widget/src/main/java/com/hzecool/widget/imgselector/adapter/ImageGridAdapter.java
new file mode 100644
index 0000000..9303e0c
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/imgselector/adapter/ImageGridAdapter.java
@@ -0,0 +1,243 @@
+package com.hzecool.widget.imgselector.adapter;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.os.Build;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+
+import com.bumptech.glide.Glide;
+import com.hzecool.widget.R;
+import com.hzecool.widget.imgselector.bean.Image;
+import com.hzecool.widget.utils.GlideSetting;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * 图片Adapter
+ * Created by Nereo on 2015/4/7.
+ * Updated by nereo on 2016/1/19.
+ */
+public class ImageGridAdapter extends BaseAdapter {
+
+ private static final int TYPE_CAMERA = 0;
+ private static final int TYPE_NORMAL = 1;
+
+ private Context mContext;
+
+ private LayoutInflater mInflater;
+ private boolean showCamera = true;
+ private boolean showSelectIndicator = true;
+
+ private List mImages = new ArrayList<>();
+ private List mSelectedImages = new ArrayList<>();
+
+ final int mGridWidth;
+
+ public ImageGridAdapter(Context context, boolean showCamera, int column) {
+ mContext = context;
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ this.showCamera = showCamera;
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ int width = 0;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
+ Point size = new Point();
+ wm.getDefaultDisplay().getSize(size);
+ width = size.x;
+ } else {
+ width = wm.getDefaultDisplay().getWidth();
+ }
+ mGridWidth = width / column;
+ }
+
+ /**
+ * 显示选择指示器
+ *
+ * @param b
+ */
+ public void showSelectIndicator(boolean b) {
+ showSelectIndicator = b;
+ }
+
+ public void setShowCamera(boolean b) {
+ if (showCamera == b) return;
+
+ showCamera = b;
+ notifyDataSetChanged();
+ }
+
+ public boolean isShowCamera() {
+ return showCamera;
+ }
+
+ /**
+ * 选择某个图片,改变选择状态
+ *
+ * @param image
+ */
+ public void select(Image image) {
+ if (mSelectedImages.contains(image)) {
+ mSelectedImages.remove(image);
+ } else {
+ mSelectedImages.add(image);
+ }
+ notifyDataSetChanged();
+ }
+
+ /**
+ * 通过图片路径设置默认选择
+ *
+ * @param resultList
+ */
+ public void setDefaultSelected(ArrayList resultList) {
+ for (String path : resultList) {
+ Image image = getImageByPath(path);
+ if (image != null) {
+ mSelectedImages.add(image);
+ }
+ }
+ if (mSelectedImages.size() > 0) {
+ notifyDataSetChanged();
+ }
+ }
+
+ private Image getImageByPath(String path) {
+ if (mImages != null && mImages.size() > 0) {
+ for (Image image : mImages) {
+ if (image.path.equalsIgnoreCase(path)) {
+ return image;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 设置数据集
+ *
+ * @param images
+ */
+ public void setData(List images) {
+ mSelectedImages.clear();
+
+ if (images != null && images.size() > 0) {
+ mImages = images;
+ } else {
+ mImages.clear();
+ }
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (showCamera) {
+ return position == 0 ? TYPE_CAMERA : TYPE_NORMAL;
+ }
+ return TYPE_NORMAL;
+ }
+
+ @Override
+ public int getCount() {
+ return showCamera ? mImages.size() + 1 : mImages.size();
+ }
+
+ @Override
+ public Image getItem(int i) {
+ if (showCamera) {
+ if (i == 0) {
+ return null;
+ }
+ return mImages.get(i - 1);
+ } else {
+ return mImages.get(i);
+ }
+ }
+
+ @Override
+ public long getItemId(int i) {
+ return i;
+ }
+
+ @Override
+ public View getView(int i, View view, ViewGroup viewGroup) {
+
+ if (isShowCamera()) {
+ if (i == 0) {
+ view = mInflater.inflate(R.layout.mis_list_item_camera, viewGroup, false);
+ return view;
+ }
+ }
+
+ ViewHolder holder;
+ if (view == null) {
+ view = mInflater.inflate(R.layout.mis_list_item_image, viewGroup, false);
+ holder = new ViewHolder(view);
+ } else {
+ holder = (ViewHolder) view.getTag();
+ }
+
+ if (holder != null) {
+ holder.bindData(getItem(i));
+ }
+
+ return view;
+ }
+
+ class ViewHolder {
+ ImageView image;
+ ImageView indicator;
+ View mask;
+
+ ViewHolder(View view) {
+ image = (ImageView) view.findViewById(R.id.image);
+ indicator = (ImageView) view.findViewById(R.id.checkmark);
+ mask = view.findViewById(R.id.mask);
+ view.setTag(this);
+ }
+
+ void bindData(final Image data) {
+ if (data == null) return;
+ // 处理单选和多选状态
+ if (showSelectIndicator) {
+ indicator.setVisibility(View.VISIBLE);
+ if (mSelectedImages.contains(data)) {
+ // 设置选中状态
+ indicator.setImageResource(R.mipmap.mis_btn_selected);
+ mask.setVisibility(View.VISIBLE);
+ } else {
+ // 未选择
+ indicator.setImageResource(R.mipmap.mis_btn_unselected);
+ mask.setVisibility(View.GONE);
+ }
+ } else {
+ indicator.setVisibility(View.GONE);
+ }
+ File imageFile = new File(data.path);
+ if (imageFile.exists()) {
+
+ Glide.with(mContext)
+ .load(imageFile)
+ .apply(GlideSetting.getGlideSetting()
+ .placeholder(R.mipmap.mis_default_error)
+ .override(mGridWidth, mGridWidth)
+ .centerCrop())
+ .into(image);
+ } else {
+ image.setImageResource(R.mipmap.mis_default_error);
+ }
+ }
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/imgselector/bean/Folder.java b/widget/src/main/java/com/hzecool/widget/imgselector/bean/Folder.java
new file mode 100644
index 0000000..3981711
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/imgselector/bean/Folder.java
@@ -0,0 +1,27 @@
+package com.hzecool.widget.imgselector.bean;
+
+import android.text.TextUtils;
+
+import java.util.List;
+
+/**
+ * 文件夹
+ * Created by Nereo on 2015/4/7.
+ */
+public class Folder {
+ public String name;
+ public String path;
+ public Image cover;
+ public List images;
+
+ @Override
+ public boolean equals(Object o) {
+ try {
+ Folder other = (Folder) o;
+ return TextUtils.equals(other.path, path);
+ }catch (ClassCastException e){
+ e.printStackTrace();
+ }
+ return super.equals(o);
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/imgselector/bean/Image.java b/widget/src/main/java/com/hzecool/widget/imgselector/bean/Image.java
new file mode 100644
index 0000000..0fd1bbc
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/imgselector/bean/Image.java
@@ -0,0 +1,30 @@
+package com.hzecool.widget.imgselector.bean;
+
+import android.text.TextUtils;
+
+/**
+ * 图片实体
+ * Created by Nereo on 2015/4/7.
+ */
+public class Image {
+ public String path;
+ public String name;
+ public long time;
+
+ public Image(String path, String name, long time){
+ this.path = path;
+ this.name = name;
+ this.time = time;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ try {
+ Image other = (Image) o;
+ return TextUtils.equals(this.path, other.path);
+ }catch (ClassCastException e){
+ e.printStackTrace();
+ }
+ return super.equals(o);
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/imgselector/utils/FileUtils.java b/widget/src/main/java/com/hzecool/widget/imgselector/utils/FileUtils.java
new file mode 100644
index 0000000..57c0885
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/imgselector/utils/FileUtils.java
@@ -0,0 +1,129 @@
+package com.hzecool.widget.imgselector.utils;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Environment;
+import android.text.TextUtils;
+
+import java.io.File;
+import java.io.IOException;
+
+import static android.os.Environment.MEDIA_MOUNTED;
+
+/**
+ * 文件操作类
+ * Created by Nereo on 2015/4/8.
+ */
+public class FileUtils {
+
+ private static final String JPEG_FILE_PREFIX = "IMG_";
+ private static final String JPEG_FILE_SUFFIX = ".jpg";
+
+ public static File createTmpFile(Context context) throws IOException{
+ File dir = null;
+ if(TextUtils.equals(Environment.getExternalStorageState(), Environment.MEDIA_MOUNTED)) {
+ dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
+ if (!dir.exists()) {
+ dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM + "/Camera");
+ if (!dir.exists()) {
+ dir = getCacheDirectory(context, true);
+ }
+ }
+ }else{
+ dir = getCacheDirectory(context, true);
+ }
+ return File.createTempFile(JPEG_FILE_PREFIX, JPEG_FILE_SUFFIX, dir);
+ }
+
+
+ private static final String EXTERNAL_STORAGE_PERMISSION = "android.permission.WRITE_EXTERNAL_STORAGE";
+
+ /**
+ * Returns application cache directory. Cache directory will be created on SD card
+ * ("/Android/data/[app_package_name]/cache") if card is mounted and app has appropriate permission. Else -
+ * Android defines cache directory on device's file system.
+ *
+ * @param context Application context
+ * @return Cache {@link File directory}.
+ * NOTE: Can be null in some unpredictable cases (if SD card is unmounted and
+ * {@link Context#getCacheDir() Context.getCacheDir()} returns null).
+ */
+ public static File getCacheDirectory(Context context) {
+ return getCacheDirectory(context, true);
+ }
+
+ /**
+ * Returns application cache directory. Cache directory will be created on SD card
+ * ("/Android/data/[app_package_name]/cache") (if card is mounted and app has appropriate permission) or
+ * on device's file system depending incoming parameters.
+ *
+ * @param context Application context
+ * @param preferExternal Whether prefer external location for cache
+ * @return Cache {@link File directory}.
+ * NOTE: Can be null in some unpredictable cases (if SD card is unmounted and
+ * {@link Context#getCacheDir() Context.getCacheDir()} returns null).
+ */
+ public static File getCacheDirectory(Context context, boolean preferExternal) {
+ File appCacheDir = null;
+ String externalStorageState;
+ try {
+ externalStorageState = Environment.getExternalStorageState();
+ } catch (NullPointerException e) { // (sh)it happens (Issue #660)
+ externalStorageState = "";
+ } catch (IncompatibleClassChangeError e) { // (sh)it happens too (Issue #989)
+ externalStorageState = "";
+ }
+ if (preferExternal && MEDIA_MOUNTED.equals(externalStorageState) && hasExternalStoragePermission(context)) {
+ appCacheDir = getExternalCacheDir(context);
+ }
+ if (appCacheDir == null) {
+ appCacheDir = context.getCacheDir();
+ }
+ if (appCacheDir == null) {
+ String cacheDirPath = "/data/data/" + context.getPackageName() + "/cache/";
+ appCacheDir = new File(cacheDirPath);
+ }
+ return appCacheDir;
+ }
+
+ /**
+ * Returns individual application cache directory (for only image caching from ImageLoader). Cache directory will be
+ * created on SD card ("/Android/data/[app_package_name]/cache/uil-images") if card is mounted and app has
+ * appropriate permission. Else - Android defines cache directory on device's file system.
+ *
+ * @param context Application context
+ * @param cacheDir Cache directory path (e.g.: "AppCacheDir", "AppDir/cache/images")
+ * @return Cache {@link File directory}
+ */
+ public static File getIndividualCacheDirectory(Context context, String cacheDir) {
+ File appCacheDir = getCacheDirectory(context);
+ File individualCacheDir = new File(appCacheDir, cacheDir);
+ if (!individualCacheDir.exists()) {
+ if (!individualCacheDir.mkdir()) {
+ individualCacheDir = appCacheDir;
+ }
+ }
+ return individualCacheDir;
+ }
+
+ private static File getExternalCacheDir(Context context) {
+ File dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data");
+ File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache");
+ if (!appCacheDir.exists()) {
+ if (!appCacheDir.mkdirs()) {
+ return null;
+ }
+ try {
+ new File(appCacheDir, ".nomedia").createNewFile();
+ } catch (IOException e) {
+ }
+ }
+ return appCacheDir;
+ }
+
+ private static boolean hasExternalStoragePermission(Context context) {
+ int perm = context.checkCallingOrSelfPermission(EXTERNAL_STORAGE_PERMISSION);
+ return perm == PackageManager.PERMISSION_GRANTED;
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/imgselector/utils/ScreenUtils.java b/widget/src/main/java/com/hzecool/widget/imgselector/utils/ScreenUtils.java
new file mode 100644
index 0000000..d8484ee
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/imgselector/utils/ScreenUtils.java
@@ -0,0 +1,29 @@
+package com.hzecool.widget.imgselector.utils;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.os.Build;
+import android.view.Display;
+import android.view.WindowManager;
+
+/**
+ * 屏幕工具
+ * Created by nereo on 15/11/19.
+ * Updated by nereo on 2016/1/19.
+ */
+public class ScreenUtils {
+
+ public static Point getScreenSize(Context context){
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = wm.getDefaultDisplay();
+ Point out = new Point();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
+ display.getSize(out);
+ }else{
+ int width = display.getWidth();
+ int height = display.getHeight();
+ out.set(width, height);
+ }
+ return out;
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/imgselector/utils/TimeUtils.java b/widget/src/main/java/com/hzecool/widget/imgselector/utils/TimeUtils.java
new file mode 100644
index 0000000..99c9e2c
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/imgselector/utils/TimeUtils.java
@@ -0,0 +1,31 @@
+package com.hzecool.widget.imgselector.utils;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * 时间处理工具
+ * Created by Nereo on 2015/4/8.
+ */
+public class TimeUtils {
+
+ public static String timeFormat(long timeMillis, String pattern){
+ SimpleDateFormat format = new SimpleDateFormat(pattern, Locale.CHINA);
+ return format.format(new Date(timeMillis));
+ }
+
+ public static String formatPhotoDate(long time){
+ return timeFormat(time, "yyyy-MM-dd");
+ }
+
+ public static String formatPhotoDate(String path){
+ File file = new File(path);
+ if(file.exists()){
+ long time = file.lastModified();
+ return formatPhotoDate(time);
+ }
+ return "1970-01-01";
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/imgselector/view/SquareFrameLayout.java b/widget/src/main/java/com/hzecool/widget/imgselector/view/SquareFrameLayout.java
new file mode 100644
index 0000000..7d0e43d
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/imgselector/view/SquareFrameLayout.java
@@ -0,0 +1,24 @@
+package com.hzecool.widget.imgselector.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * Created by nereo on 15/11/10.
+ */
+public class SquareFrameLayout extends FrameLayout{
+ public SquareFrameLayout(Context context) {
+ super(context);
+ }
+
+ public SquareFrameLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/imgselector/view/SquaredImageView.java b/widget/src/main/java/com/hzecool/widget/imgselector/view/SquaredImageView.java
new file mode 100644
index 0000000..96a8840
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/imgselector/view/SquaredImageView.java
@@ -0,0 +1,24 @@
+package com.hzecool.widget.imgselector.view;
+
+import android.content.Context;
+import android.support.v7.widget.AppCompatImageView;
+import android.util.AttributeSet;
+
+/**
+ * An image view which always remains square with respect to its width.
+ */
+public class SquaredImageView extends AppCompatImageView {
+ public SquaredImageView(Context context) {
+ super(context);
+ }
+
+ public SquaredImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/loadingLayout/LoadingLayout.java b/widget/src/main/java/com/hzecool/widget/loadingLayout/LoadingLayout.java
new file mode 100644
index 0000000..695ee79
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/loadingLayout/LoadingLayout.java
@@ -0,0 +1,685 @@
+package com.hzecool.widget.loadingLayout;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.annotation.ColorRes;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.hzecool.widget.R;
+
+
+/**
+ * create by tutu 2017/1/11
+ * 全局加载底层布局
+ * 提供加载成功,加载失败,网络连接失败,加载中四种状态
+ */
+public class LoadingLayout extends FrameLayout {
+
+ public final static int Success = 0;
+ public final static int Empty = 1;
+ public final static int Error = 2;
+ public final static int No_Network = 3;
+ public final static int Loading = 4;
+ private int state;
+
+ private Context mContext;
+ private View loadingPage;
+ private View errorPage;
+ private View emptyPage;
+ private View networkPage;
+ private View defineLoadingPage;
+
+ private ImageView errorImg;
+ private ImageView emptyImg;
+ private ImageView networkImg;
+
+ private TextView errorText;
+ private TextView emptyText;
+ private TextView networkText;
+
+ private TextView errorReloadBtn;
+ private TextView networkReloadBtn;
+
+ private View contentView;
+ private OnReloadListener listener;
+ private boolean isFirstVisible; //是否一开始显示contentview,默认不显示
+
+ //配置
+ private static Config mConfig = new Config();
+ private static String emptyStr = "暂无数据";
+ private static String errorStr = "加载失败,请稍后重试···";
+ private static String netwrokStr = "无网络连接,请检查网络···";
+ private static String reloadBtnStr = "点击重试";
+ private static int emptyImgId = R.mipmap.empty;
+ private static int errorImgId = R.mipmap.error;
+ private static int networkImgId = R.mipmap.no_network;
+ private static int reloadBtnId = R.drawable.selector_btn_back_gray;
+ private static int tipTextSize = 14;
+ private static int buttonTextSize = 14;
+ private static int tipTextColor = R.color.black;
+ private static int buttonTextColor = R.color.black;
+ private static int buttonWidth = -1;
+ private static int buttonHeight = -1;
+ private static int loadingLayoutId = R.layout.widget_loading_page;
+ private static int backgroundColor = R.color.loading_background;
+
+ public LoadingLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ this.mContext = context;
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LoadingLayout);
+ isFirstVisible = a.getBoolean(R.styleable.LoadingLayout_isFirstVisible, false);
+ a.recycle();
+ }
+
+ public LoadingLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ this.mContext = context;
+ }
+
+ public LoadingLayout(Context context) {
+ super(context);
+ this.mContext = context;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ if (getChildCount() > 1) {
+ throw new IllegalStateException("LoadingLayout can host only one direct child");
+ }
+ contentView = this.getChildAt(0);
+ if (!isFirstVisible) {
+ contentView.setVisibility(View.GONE);
+ }
+ build();
+ }
+
+ private void build() {
+
+ loadingPage = LayoutInflater.from(mContext).inflate(loadingLayoutId, null);
+ errorPage = LayoutInflater.from(mContext).inflate(R.layout.widget_error_page, null);
+ emptyPage = LayoutInflater.from(mContext).inflate(R.layout.widget_empty_page, null);
+ networkPage = LayoutInflater.from(mContext).inflate(R.layout.widget_nonetwork_page, null);
+ defineLoadingPage = null;
+ loadingPage.setBackgroundColor(LoadingLayoutUtils.getColor(mContext, backgroundColor));
+ errorPage.setBackgroundColor(LoadingLayoutUtils.getColor(mContext, backgroundColor));
+ emptyPage.setBackgroundColor(LoadingLayoutUtils.getColor(mContext, backgroundColor));
+ networkPage.setBackgroundColor(LoadingLayoutUtils.getColor(mContext, backgroundColor));
+
+ errorText = LoadingLayoutUtils.findViewById(errorPage, R.id.error_text);
+ emptyText = LoadingLayoutUtils.findViewById(emptyPage, R.id.empty_text);
+ networkText = LoadingLayoutUtils.findViewById(networkPage, R.id.no_network_text);
+
+ errorImg = LoadingLayoutUtils.findViewById(errorPage, R.id.error_img);
+ emptyImg = LoadingLayoutUtils.findViewById(emptyPage, R.id.empty_img);
+ networkImg = LoadingLayoutUtils.findViewById(networkPage, R.id.no_network_img);
+
+ errorReloadBtn = LoadingLayoutUtils.findViewById(errorPage, R.id.error_reload_btn);
+ networkReloadBtn = LoadingLayoutUtils.findViewById(networkPage, R.id.no_network_reload_btn);
+
+ errorReloadBtn.setOnClickListener(v -> {
+
+ if (listener != null) {
+ listener.onReload(v);
+ }
+ });
+ networkReloadBtn.setOnClickListener(v -> {
+
+ if (listener != null) {
+ listener.onReload(v);
+ }
+ });
+
+ errorText.setText(errorStr);
+ emptyText.setText(emptyStr);
+ networkText.setText(netwrokStr);
+
+ errorText.setTextSize(tipTextSize);
+ emptyText.setTextSize(tipTextSize);
+ networkText.setTextSize(tipTextSize);
+
+ errorText.setTextColor(LoadingLayoutUtils.getColor(mContext, tipTextColor));
+ emptyText.setTextColor(LoadingLayoutUtils.getColor(mContext, tipTextColor));
+ networkText.setTextColor(LoadingLayoutUtils.getColor(mContext, tipTextColor));
+
+ errorImg.setImageResource(errorImgId);
+ emptyImg.setImageResource(emptyImgId);
+ networkImg.setImageResource(networkImgId);
+
+
+ errorReloadBtn.setBackgroundResource(reloadBtnId);
+ networkReloadBtn.setBackgroundResource(reloadBtnId);
+
+ errorReloadBtn.setText(reloadBtnStr);
+ networkReloadBtn.setText(reloadBtnStr);
+
+ errorReloadBtn.setTextSize(buttonTextSize);
+ networkReloadBtn.setTextSize(buttonTextSize);
+
+ errorReloadBtn.setTextColor(LoadingLayoutUtils.getColor(mContext, buttonTextColor));
+ networkReloadBtn.setTextColor(LoadingLayoutUtils.getColor(mContext, buttonTextColor));
+
+ if (buttonHeight != -1) {
+
+ errorReloadBtn.setHeight(LoadingLayoutUtils.dp2px(mContext, buttonHeight));
+ networkReloadBtn.setHeight(LoadingLayoutUtils.dp2px(mContext, buttonHeight));
+ }
+ if (buttonWidth != -1) {
+
+ errorReloadBtn.setWidth(LoadingLayoutUtils.dp2px(mContext, buttonWidth));
+ networkReloadBtn.setWidth(LoadingLayoutUtils.dp2px(mContext, buttonWidth));
+ }
+
+ this.addView(networkPage);
+ this.addView(emptyPage);
+ this.addView(errorPage);
+ this.addView(loadingPage);
+ }
+
+ public void setStatus(@Flavour int status) {
+
+ this.state = status;
+
+ switch (status) {
+ case Success:
+
+ contentView.setVisibility(View.VISIBLE);
+ emptyPage.setVisibility(View.GONE);
+ errorPage.setVisibility(View.GONE);
+ networkPage.setVisibility(View.GONE);
+ if (defineLoadingPage != null) {
+
+ defineLoadingPage.setVisibility(View.GONE);
+ } else {
+ loadingPage.setVisibility(View.GONE);
+ }
+ break;
+
+ case Loading:
+
+ contentView.setVisibility(View.GONE);
+ emptyPage.setVisibility(View.GONE);
+ errorPage.setVisibility(View.GONE);
+ networkPage.setVisibility(View.GONE);
+ if (defineLoadingPage != null) {
+ defineLoadingPage.setVisibility(View.VISIBLE);
+ } else {
+ loadingPage.setVisibility(View.VISIBLE);
+ }
+ break;
+
+ case Empty:
+
+ contentView.setVisibility(View.GONE);
+ emptyPage.setVisibility(View.VISIBLE);
+ errorPage.setVisibility(View.GONE);
+ networkPage.setVisibility(View.GONE);
+ if (defineLoadingPage != null) {
+ defineLoadingPage.setVisibility(View.GONE);
+ } else {
+ loadingPage.setVisibility(View.GONE);
+ }
+ break;
+
+ case Error:
+
+ contentView.setVisibility(View.GONE);
+ loadingPage.setVisibility(View.GONE);
+ emptyPage.setVisibility(View.GONE);
+ errorPage.setVisibility(View.VISIBLE);
+ networkPage.setVisibility(View.GONE);
+ if (defineLoadingPage != null) {
+ defineLoadingPage.setVisibility(View.GONE);
+ } else {
+ loadingPage.setVisibility(View.GONE);
+ }
+ break;
+
+ case No_Network:
+
+ contentView.setVisibility(View.GONE);
+ loadingPage.setVisibility(View.GONE);
+ emptyPage.setVisibility(View.GONE);
+ errorPage.setVisibility(View.GONE);
+ networkPage.setVisibility(View.VISIBLE);
+ if (defineLoadingPage != null) {
+
+ defineLoadingPage.setVisibility(View.GONE);
+ } else {
+ loadingPage.setVisibility(View.GONE);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ }
+
+
+ /**
+ * 返回当前状态{Success, Empty, Error, No_Network, Loading}
+ *
+ * @return
+ */
+ public int getStatus() {
+
+ return state;
+ }
+
+ /**
+ * 设置Empty状态提示文本,仅对当前所在的地方有效
+ *
+ * @param text
+ * @return
+ */
+ public LoadingLayout setEmptyText(String text) {
+
+ emptyText.setText(text);
+ return this;
+ }
+
+ /**
+ * 设置Error状态提示文本,仅对当前所在的地方有效
+ *
+ * @param text
+ * @return
+ */
+ public LoadingLayout setErrorText(String text) {
+
+ errorText.setText(text);
+ return this;
+ }
+
+ /**
+ * 设置No_Network状态提示文本,仅对当前所在的地方有效
+ *
+ * @param text
+ * @return
+ */
+ public LoadingLayout setNoNetworkText(String text) {
+
+ networkText.setText(text);
+ return this;
+ }
+
+ /**
+ * 设置Empty状态显示图片,仅对当前所在的地方有效
+ *
+ * @param id
+ * @return
+ */
+ public LoadingLayout setEmptyImage(@DrawableRes int id) {
+
+
+ emptyImg.setImageResource(id);
+ return this;
+ }
+
+ /**
+ * 设置Error状态显示图片,仅对当前所在的地方有效
+ *
+ * @param id
+ * @return
+ */
+ public LoadingLayout setErrorImage(@DrawableRes int id) {
+
+ errorImg.setImageResource(id);
+ return this;
+ }
+
+ /**
+ * 设置No_Network状态显示图片,仅对当前所在的地方有效
+ *
+ * @param id
+ * @return
+ */
+ public LoadingLayout setNoNetworkImage(@DrawableRes int id) {
+
+ networkImg.setImageResource(id);
+ return this;
+ }
+
+ /**
+ * 设置Empty状态提示文本的字体大小,仅对当前所在的地方有效
+ *
+ * @param sp
+ * @return
+ */
+ public LoadingLayout setEmptyTextSize(int sp) {
+
+ emptyText.setTextSize(sp);
+ return this;
+ }
+
+ /**
+ * 设置Error状态提示文本的字体大小,仅对当前所在的地方有效
+ *
+ * @param sp
+ * @return
+ */
+ public LoadingLayout setErrorTextSize(int sp) {
+
+ errorText.setTextSize(sp);
+ return this;
+ }
+
+ /**
+ * 设置No_Network状态提示文本的字体大小,仅对当前所在的地方有效
+ *
+ * @param sp
+ * @return
+ */
+ public LoadingLayout setNoNetworkTextSize(int sp) {
+
+ networkText.setTextSize(sp);
+ return this;
+ }
+
+ /**
+ * 设置Empty状态图片的显示与否,仅对当前所在的地方有效
+ *
+ * @param bool
+ * @return
+ */
+ public LoadingLayout setEmptyImageVisible(boolean bool) {
+
+ if (bool) {
+ emptyImg.setVisibility(View.VISIBLE);
+ } else {
+ emptyImg.setVisibility(View.GONE);
+ }
+ return this;
+ }
+
+ /**
+ * 设置Error状态图片的显示与否,仅对当前所在的地方有效
+ *
+ * @param bool
+ * @return
+ */
+ public LoadingLayout setErrorImageVisible(boolean bool) {
+
+ if (bool) {
+ errorImg.setVisibility(View.VISIBLE);
+ } else {
+ errorImg.setVisibility(View.GONE);
+ }
+ return this;
+ }
+
+ /**
+ * 设置No_Network状态图片的显示与否,仅对当前所在的地方有效
+ *
+ * @param bool
+ * @return
+ */
+ public LoadingLayout setNoNetworkImageVisible(boolean bool) {
+
+ if (bool) {
+ networkImg.setVisibility(View.VISIBLE);
+ } else {
+ networkImg.setVisibility(View.GONE);
+ }
+ return this;
+ }
+
+ /**
+ * 设置ReloadButton的文本,仅对当前所在的地方有效
+ *
+ * @param text
+ * @return
+ */
+ public LoadingLayout setReloadButtonText(@NonNull String text) {
+
+ errorReloadBtn.setText(text);
+ networkReloadBtn.setText(text);
+ return this;
+ }
+
+ /**
+ * 设置ReloadButton的文本字体大小,仅对当前所在的地方有效
+ *
+ * @param sp
+ * @return
+ */
+ public LoadingLayout setReloadButtonTextSize(int sp) {
+
+ errorReloadBtn.setTextSize(sp);
+ networkReloadBtn.setTextSize(sp);
+ return this;
+ }
+
+ /**
+ * 设置ReloadButton的文本颜色,仅对当前所在的地方有效
+ *
+ * @param id
+ * @return
+ */
+ public LoadingLayout setReloadButtonTextColor(@ColorRes int id) {
+
+ errorReloadBtn.setTextColor(LoadingLayoutUtils.getColor(mContext, id));
+ networkReloadBtn.setTextSize(LoadingLayoutUtils.getColor(mContext, id));
+ return this;
+ }
+
+ /**
+ * 设置ReloadButton的背景,仅对当前所在的地方有效
+ *
+ * @param id
+ * @return
+ */
+ public LoadingLayout setReloadButtonBackgroundResource(@DrawableRes int id) {
+
+ errorReloadBtn.setBackgroundResource(id);
+ networkReloadBtn.setBackgroundResource(id);
+ return this;
+ }
+
+ /**
+ * 设置ReloadButton的监听器
+ *
+ * @param listener
+ * @return
+ */
+ public LoadingLayout setOnReloadListener(OnReloadListener listener) {
+
+ this.listener = listener;
+ return this;
+ }
+
+ /**
+ * 自定义加载页面,仅对当前所在的Activity有效
+ *
+ * @param view
+ * @return
+ */
+ public LoadingLayout setLoadingPage(View view) {
+
+ defineLoadingPage = view;
+ this.removeView(loadingPage);
+ defineLoadingPage.setVisibility(View.GONE);
+ this.addView(view);
+ return this;
+ }
+
+ public LoadingLayout setEmptyImageViewClickListener(OnClickListener viewClickListener){
+ emptyImg.setOnClickListener(viewClickListener);
+ return this;
+ }
+
+ /**
+ * 自定义加载页面,仅对当前所在的地方有效
+ *
+ * @param id
+ * @return
+ */
+ public LoadingLayout setLoadingPage(@LayoutRes int id) {
+
+ this.removeView(loadingPage);
+ View view = LayoutInflater.from(mContext).inflate(id, null);
+ defineLoadingPage = view;
+ defineLoadingPage.setVisibility(View.GONE);
+ this.addView(view);
+ return this;
+ }
+
+ /**
+ * 获取当前自定义的loadingpage
+ *
+ * @return
+ */
+ public View getLoadingPage() {
+
+ return defineLoadingPage;
+ }
+
+
+ /**
+ * 获取全局使用的loadingpage
+ *
+ * @return
+ */
+ public View getGlobalLoadingPage() {
+
+ return loadingPage;
+ }
+
+ @IntDef({Success, Empty, Error, No_Network, Loading})
+ public @interface Flavour {
+
+ }
+
+ public interface OnReloadListener {
+
+ void onReload(View v);
+ }
+
+ /**
+ * 获取全局配置的class
+ *
+ * @return
+ */
+ public static Config getConfig() {
+
+ return mConfig;
+ }
+
+ /**
+ * 全局配置的Class,对所有使用到的地方有效
+ */
+ public static class Config {
+
+ public Config setErrorText(@NonNull String text) {
+
+ errorStr = text;
+ return mConfig;
+ }
+
+ public Config setEmptyText(@NonNull String text) {
+
+ emptyStr = text;
+ return mConfig;
+ }
+
+ public Config setNoNetworkText(@NonNull String text) {
+
+ netwrokStr = text;
+ return mConfig;
+ }
+
+ public Config setReloadButtonText(@NonNull String text) {
+
+ reloadBtnStr = text;
+ return mConfig;
+ }
+
+ /**
+ * 设置所有提示文本的字体大小
+ *
+ * @param sp
+ * @return
+ */
+ public Config setAllTipTextSize(int sp) {
+
+ tipTextSize = sp;
+ return mConfig;
+ }
+
+ /**
+ * 设置所有提示文本的字体颜色
+ *
+ * @param color
+ * @return
+ */
+ public Config setAllTipTextColor(@ColorRes int color) {
+
+ tipTextColor = color;
+ return mConfig;
+ }
+
+ public Config setReloadButtonTextSize(int sp) {
+
+ buttonTextSize = sp;
+ return mConfig;
+ }
+
+ public Config setReloadButtonTextColor(@ColorRes int color) {
+
+ buttonTextColor = color;
+ return mConfig;
+ }
+
+ public Config setReloadButtonBackgroundResource(@DrawableRes int id) {
+
+ reloadBtnId = id;
+ return mConfig;
+ }
+
+ public Config setReloadButtonWidthAndHeight(int width_dp, int height_dp) {
+
+ buttonWidth = width_dp;
+ buttonHeight = height_dp;
+ return mConfig;
+ }
+
+ public Config setErrorImage(@DrawableRes int id) {
+
+ errorImgId = id;
+ return mConfig;
+ }
+
+ public Config setEmptyImage(@DrawableRes int id) {
+
+ emptyImgId = id;
+ return this;
+ }
+
+ public Config setNoNetworkImage(@DrawableRes int id) {
+
+ networkImgId = id;
+ return mConfig;
+ }
+
+ public Config setLoadingPageLayout(@LayoutRes int id) {
+
+ loadingLayoutId = id;
+ return mConfig;
+ }
+
+ public Config setAllPageBackgroundColor(@ColorRes int color) {
+
+ backgroundColor = color;
+ return mConfig;
+ }
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/loadingLayout/LoadingLayoutUtils.java b/widget/src/main/java/com/hzecool/widget/loadingLayout/LoadingLayoutUtils.java
new file mode 100644
index 0000000..81d26f6
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/loadingLayout/LoadingLayoutUtils.java
@@ -0,0 +1,48 @@
+package com.hzecool.widget.loadingLayout;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorRes;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.StringRes;
+import android.support.v4.content.ContextCompat;
+import android.view.View;
+
+/**
+ * create by tutu
+ * on date 2017-01-11
+ */
+
+public class LoadingLayoutUtils {
+
+
+ public static Drawable getDrawble(Context conetxt, @DrawableRes int id) {
+ return ContextCompat.getDrawable(conetxt, id);
+ }
+
+ public static int getColor(Context conetxt, @ColorRes int id) {
+ return ContextCompat.getColor(conetxt, id);
+ }
+
+ public static String getString(Context conetxt, @StringRes int id) {
+ return conetxt.getResources().getString(id);
+ }
+
+ public static int sp2px(Context context, float spValue) {
+ final float fontScale = context.getResources().getDisplayMetrics()
+ .scaledDensity;
+ return (int) (spValue * fontScale + 0.5f);
+ }
+
+ public static int dp2px(Context context, int dip) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (dip * scale + 0.5f);
+ }
+
+ public static T findViewById(View v, int id) {
+
+
+ return (T) v.findViewById(id);
+ }
+
+}
\ No newline at end of file
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/DefaultRvAdapter.java b/widget/src/main/java/com/hzecool/widget/materialdialog/DefaultRvAdapter.java
new file mode 100644
index 0000000..22970d4
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/DefaultRvAdapter.java
@@ -0,0 +1,210 @@
+package com.hzecool.widget.materialdialog;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.support.annotation.LayoutRes;
+import android.support.v7.widget.RecyclerView;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+import com.hzecool.widget.R;
+import com.hzecool.widget.materialdialog.internal.MDTintHelper;
+import com.hzecool.widget.materialdialog.util.DialogUtils;
+
+
+/**
+ * @author Aidan Follestad (afollestad)
+ */
+class DefaultRvAdapter extends RecyclerView.Adapter {
+
+ private final MaterialDialog dialog;
+ @LayoutRes
+ private final int layout;
+ private final GravityEnum itemGravity;
+ private InternalListCallback callback;
+
+ DefaultRvAdapter(MaterialDialog dialog, @LayoutRes int layout) {
+ this.dialog = dialog;
+ this.layout = layout;
+ this.itemGravity = dialog.builder.itemsGravity;
+ }
+
+ void setCallback(InternalListCallback callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public DefaultVH onCreateViewHolder(ViewGroup parent, int viewType) {
+ final View view = LayoutInflater.from(parent.getContext())
+ .inflate(layout, parent, false);
+ DialogUtils.setBackgroundCompat(view, dialog.getListSelector());
+ return new DefaultVH(view, this);
+ }
+
+ @Override
+ public void onBindViewHolder(DefaultVH holder, int index) {
+ final View view = holder.itemView;
+ boolean disabled = DialogUtils.isIn(index, dialog.builder.disabledIndices);
+ switch (dialog.listType) {
+ case SINGLE: {
+ @SuppressLint("CutPasteId") RadioButton radio = (RadioButton) holder.control;
+ boolean selected = dialog.builder.selectedIndex == index;
+ if (dialog.builder.choiceWidgetColor != null) {
+ MDTintHelper.setTint(radio, dialog.builder.choiceWidgetColor);
+ } else {
+ MDTintHelper.setTint(radio, dialog.builder.widgetColor);
+ }
+ radio.setChecked(selected);
+ radio.setEnabled(!disabled);
+ break;
+ }
+ case MULTI: {
+ @SuppressLint("CutPasteId") CheckBox checkbox = (CheckBox) holder.control;
+ boolean selected = dialog.selectedIndicesList.contains(index);
+ if (dialog.builder.choiceWidgetColor != null) {
+ MDTintHelper.setTint(checkbox, dialog.builder.choiceWidgetColor);
+ } else {
+ MDTintHelper.setTint(checkbox, dialog.builder.widgetColor);
+ }
+ checkbox.setChecked(selected);
+ checkbox.setEnabled(!disabled);
+ break;
+ }
+ }
+
+ holder.title.setText(dialog.builder.items.get(index));
+ holder.title.setTextColor(dialog.builder.itemColor);
+ dialog.setTypeface(holder.title, dialog.builder.regularFont);
+
+ setupGravity((ViewGroup) view);
+
+ if (dialog.builder.itemIds != null) {
+ if (index < dialog.builder.itemIds.length) {
+ view.setId(dialog.builder.itemIds[index]);
+ } else {
+ view.setId(-1);
+ }
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ ViewGroup group = (ViewGroup) view;
+ if (group.getChildCount() == 2) {
+ // Remove circular selector from check boxes and radio buttons on Lollipop
+ if (group.getChildAt(0) instanceof CompoundButton) {
+ group.getChildAt(0).setBackground(null);
+ } else if (group.getChildAt(1) instanceof CompoundButton) {
+ group.getChildAt(1).setBackground(null);
+ }
+ }
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return dialog.builder.items != null ? dialog.builder.items.size() : 0;
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ private void setupGravity(ViewGroup view) {
+ final LinearLayout itemRoot = (LinearLayout) view;
+ final int gravityInt = itemGravity.getGravityInt();
+ itemRoot.setGravity(gravityInt | Gravity.CENTER_VERTICAL);
+
+ if (view.getChildCount() == 2) {
+ if (itemGravity == GravityEnum.END && !isRTL() && view
+ .getChildAt(0) instanceof CompoundButton) {
+ CompoundButton first = (CompoundButton) view.getChildAt(0);
+ view.removeView(first);
+
+ TextView second = (TextView) view.getChildAt(0);
+ view.removeView(second);
+ second.setPadding(second.getPaddingRight(), second.getPaddingTop(),
+ second.getPaddingLeft(), second.getPaddingBottom());
+
+ view.addView(second);
+ view.addView(first);
+ } else if (itemGravity == GravityEnum.START && isRTL() && view
+ .getChildAt(1) instanceof CompoundButton) {
+ CompoundButton first = (CompoundButton) view.getChildAt(1);
+ view.removeView(first);
+
+ TextView second = (TextView) view.getChildAt(0);
+ view.removeView(second);
+ second.setPadding(second.getPaddingRight(), second.getPaddingTop(),
+ second.getPaddingRight(), second.getPaddingBottom());
+
+ view.addView(first);
+ view.addView(second);
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ private boolean isRTL() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return false;
+ }
+ Configuration config = dialog.getBuilder().getContext().getResources().getConfiguration();
+ return config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ }
+
+ interface InternalListCallback {
+
+ boolean onItemSelected(MaterialDialog dialog, View itemView, int position, CharSequence text,
+ boolean longPress);
+ }
+
+ static class DefaultVH extends RecyclerView.ViewHolder implements View.OnClickListener,
+ View.OnLongClickListener {
+
+ final CompoundButton control;
+ final TextView title;
+ final DefaultRvAdapter adapter;
+
+ DefaultVH(View itemView, DefaultRvAdapter adapter) {
+ super(itemView);
+ control = (CompoundButton) itemView.findViewById(R.id.md_control);
+ title = (TextView) itemView.findViewById(R.id.md_title);
+ this.adapter = adapter;
+ itemView.setOnClickListener(this);
+ if (adapter.dialog.builder.listLongCallback != null) {
+ itemView.setOnLongClickListener(this);
+ }
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (adapter.callback != null && getAdapterPosition() != RecyclerView.NO_POSITION) {
+ CharSequence text = null;
+ if (adapter.dialog.builder.items != null &&
+ getAdapterPosition() < adapter.dialog.builder.items.size()) {
+ text = adapter.dialog.builder.items.get(getAdapterPosition());
+ }
+ adapter.callback.onItemSelected(adapter.dialog, view, getAdapterPosition(), text, false);
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View view) {
+ if (adapter.callback != null && getAdapterPosition() != RecyclerView.NO_POSITION) {
+ CharSequence text = null;
+ if (adapter.dialog.builder.items != null &&
+ getAdapterPosition() < adapter.dialog.builder.items.size()) {
+ text = adapter.dialog.builder.items.get(getAdapterPosition());
+ }
+ return adapter.callback
+ .onItemSelected(adapter.dialog, view, getAdapterPosition(), text, true);
+ }
+ return false;
+ }
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/DialogAction.java b/widget/src/main/java/com/hzecool/widget/materialdialog/DialogAction.java
new file mode 100644
index 0000000..d6ae2dd
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/DialogAction.java
@@ -0,0 +1,10 @@
+package com.hzecool.widget.materialdialog;
+
+/**
+ * @author Aidan Follestad (afollestad)
+ */
+public enum DialogAction {
+ POSITIVE,
+ NEUTRAL,
+ NEGATIVE
+}
\ No newline at end of file
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/DialogBase.java b/widget/src/main/java/com/hzecool/widget/materialdialog/DialogBase.java
new file mode 100644
index 0000000..e4cfef8
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/DialogBase.java
@@ -0,0 +1,71 @@
+package com.hzecool.widget.materialdialog;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.support.annotation.NonNull;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.hzecool.widget.materialdialog.internal.MDRootLayout;
+
+
+/**
+ * @author Aidan Follestad (afollestad)
+ */
+class DialogBase extends Dialog implements DialogInterface.OnShowListener {
+
+ protected MDRootLayout view;
+ private OnShowListener showListener;
+
+ DialogBase(Context context, int theme) {
+ super(context, theme);
+ }
+
+ @Override
+ public View findViewById(int id) {
+ return view.findViewById(id);
+ }
+
+ @Override
+ public final void setOnShowListener(OnShowListener listener) {
+ showListener = listener;
+ }
+
+ final void setOnShowListenerInternal() {
+ super.setOnShowListener(this);
+ }
+
+ final void setViewInternal(View view) {
+ super.setContentView(view);
+ }
+
+ @Override
+ public void onShow(DialogInterface dialog) {
+ if (showListener != null) {
+ showListener.onShow(dialog);
+ }
+ }
+
+ @Override
+ @Deprecated
+ public void setContentView(int layoutResID) throws IllegalAccessError {
+ throw new IllegalAccessError(
+ "setContentView() is not supported in MaterialDialog. Specify a custom view in the Builder instead.");
+ }
+
+ @Override
+ @Deprecated
+ public void setContentView(@NonNull View view) throws IllegalAccessError {
+ throw new IllegalAccessError(
+ "setContentView() is not supported in MaterialDialog. Specify a custom view in the Builder instead.");
+ }
+
+ @Override
+ @Deprecated
+ public void setContentView(@NonNull View view, ViewGroup.LayoutParams params)
+ throws IllegalAccessError {
+ throw new IllegalAccessError(
+ "setContentView() is not supported in MaterialDialog. Specify a custom view in the Builder instead.");
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/DialogInit.java b/widget/src/main/java/com/hzecool/widget/materialdialog/DialogInit.java
new file mode 100644
index 0000000..8bb6d25
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/DialogInit.java
@@ -0,0 +1,554 @@
+package com.hzecool.widget.materialdialog;
+
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.os.Build;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.StyleRes;
+import android.support.annotation.UiThread;
+import android.support.v7.widget.RecyclerView;
+import android.text.InputType;
+import android.text.method.LinkMovementMethod;
+import android.text.method.PasswordTransformationMethod;
+import android.view.Display;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import com.hzecool.widget.R;
+import com.hzecool.widget.materialdialog.internal.MDAdapter;
+import com.hzecool.widget.materialdialog.internal.MDButton;
+import com.hzecool.widget.materialdialog.internal.MDRootLayout;
+import com.hzecool.widget.materialdialog.internal.MDTintHelper;
+import com.hzecool.widget.materialdialog.util.DialogUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import me.zhanghai.android.materialprogressbar.HorizontalProgressDrawable;
+import me.zhanghai.android.materialprogressbar.IndeterminateHorizontalProgressDrawable;
+import me.zhanghai.android.materialprogressbar.IndeterminateProgressDrawable;
+
+
+/**
+ * Used by MaterialDialog while initializing the dialog. Offloads some of the code to make the main
+ * class cleaner and easier to read/maintain.
+ *
+ * @author Aidan Follestad (afollestad)
+ */
+class DialogInit {
+
+ @StyleRes
+ static int getTheme(@NonNull MaterialDialog.Builder builder) {
+ boolean darkTheme = DialogUtils
+ .resolveBoolean(builder.context, R.attr.md_dark_theme, builder.theme == Theme.DARK);
+ builder.theme = darkTheme ? Theme.DARK : Theme.LIGHT;
+ return darkTheme ? R.style.MD_Dark : R.style.MD_Light;
+ }
+
+ @LayoutRes
+ static int getInflateLayout(MaterialDialog.Builder builder) {
+ if (builder.customView != null) {
+ return R.layout.md_dialog_custom;
+ } else if (builder.items != null || builder.adapter != null) {
+ if (builder.checkBoxPrompt != null) {
+ return R.layout.md_dialog_list_check;
+ }
+ return R.layout.md_dialog_list;
+ } else if (builder.progress > -2) {
+ return R.layout.md_dialog_progress;
+ } else if (builder.indeterminateProgress) {
+ if (builder.indeterminateIsHorizontalProgress) {
+ return R.layout.md_dialog_progress_indeterminate_horizontal;
+ }
+ return R.layout.md_dialog_progress_indeterminate;
+ } else if (builder.inputCallback != null) {
+ if (builder.checkBoxPrompt != null) {
+ return R.layout.md_dialog_input_check;
+ }
+ return R.layout.md_dialog_input;
+ } else if (builder.checkBoxPrompt != null) {
+ return R.layout.md_dialog_basic_check;
+ } else {
+ return R.layout.md_dialog_basic;
+ }
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @UiThread
+ public static void init(final MaterialDialog dialog) {
+ final MaterialDialog.Builder builder = dialog.builder;
+
+ // Set cancelable flag and dialog background color
+ dialog.setCancelable(builder.cancelable);
+ dialog.setCanceledOnTouchOutside(builder.canceledOnTouchOutside);
+ if (builder.backgroundColor == 0) {
+ builder.backgroundColor = DialogUtils
+ .resolveColor(builder.context, R.attr.md_background_color,
+ DialogUtils.resolveColor(dialog.getContext(), R.attr.colorBackgroundFloating));
+ }
+ if (builder.backgroundColor != 0) {
+ GradientDrawable drawable = new GradientDrawable();
+ drawable.setCornerRadius(
+ builder.context.getResources().getDimension(R.dimen.md_bg_corner_radius));
+ drawable.setColor(builder.backgroundColor);
+ dialog.getWindow().setBackgroundDrawable(drawable);
+ }
+
+ // Retrieve color theme attributes
+ if (!builder.positiveColorSet) {
+ builder.positiveColor = DialogUtils
+ .resolveActionTextColorStateList(builder.context, R.attr.md_positive_color,
+ builder.positiveColor);
+ }
+ if (!builder.neutralColorSet) {
+ builder.neutralColor = DialogUtils
+ .resolveActionTextColorStateList(builder.context, R.attr.md_neutral_color,
+ builder.neutralColor);
+ }
+ if (!builder.negativeColorSet) {
+ builder.negativeColor = DialogUtils
+ .resolveActionTextColorStateList(builder.context, R.attr.md_negative_color,
+ builder.negativeColor);
+ }
+ if (!builder.widgetColorSet) {
+ builder.widgetColor = DialogUtils
+ .resolveColor(builder.context, R.attr.md_widget_color, builder.widgetColor);
+ }
+
+ // Retrieve default title/content colors
+ if (!builder.titleColorSet) {
+ final int titleColorFallback = DialogUtils
+ .resolveColor(dialog.getContext(), android.R.attr.textColorPrimary);
+ builder.titleColor = DialogUtils
+ .resolveColor(builder.context, R.attr.md_title_color, titleColorFallback);
+ }
+ if (!builder.contentColorSet) {
+ final int contentColorFallback = DialogUtils
+ .resolveColor(dialog.getContext(), android.R.attr.textColorSecondary);
+ builder.contentColor = DialogUtils
+ .resolveColor(builder.context, R.attr.md_content_color, contentColorFallback);
+ }
+ if (!builder.itemColorSet) {
+ builder.itemColor = DialogUtils
+ .resolveColor(builder.context, R.attr.md_item_color, builder.contentColor);
+ }
+
+ // Retrieve references to views
+ dialog.title = (TextView) dialog.view.findViewById(R.id.md_title);
+ dialog.icon = (ImageView) dialog.view.findViewById(R.id.md_icon);
+ dialog.titleFrame = dialog.view.findViewById(R.id.md_titleFrame);
+ dialog.content = (TextView) dialog.view.findViewById(R.id.md_content);
+ dialog.recyclerView = (RecyclerView) dialog.view.findViewById(R.id.md_contentRecyclerView);
+ dialog.checkBoxPrompt = (CheckBox) dialog.view.findViewById(R.id.md_promptCheckbox);
+
+ // Button views initially used by checkIfStackingNeeded()
+ dialog.positiveButton = (MDButton) dialog.view.findViewById(R.id.md_buttonDefaultPositive);
+ dialog.neutralButton = (MDButton) dialog.view.findViewById(R.id.md_buttonDefaultNeutral);
+ dialog.negativeButton = (MDButton) dialog.view.findViewById(R.id.md_buttonDefaultNegative);
+
+ // Don't allow the submit button to not be shown for input dialogs
+ if (builder.inputCallback != null && builder.positiveText == null) {
+ builder.positiveText = builder.context.getText(android.R.string.ok);
+ }
+
+ // Set up the initial visibility of action buttons based on whether or not text was set
+ dialog.positiveButton.setVisibility(builder.positiveText != null ? View.VISIBLE : View.GONE);
+ dialog.neutralButton.setVisibility(builder.neutralText != null ? View.VISIBLE : View.GONE);
+ dialog.negativeButton.setVisibility(builder.negativeText != null ? View.VISIBLE : View.GONE);
+
+ // Set up the focus of action buttons
+ dialog.positiveButton.setFocusable(true);
+ dialog.neutralButton.setFocusable(true);
+ dialog.negativeButton.setFocusable(true);
+ if (builder.positiveFocus) {
+ dialog.positiveButton.requestFocus();
+ }
+ if (builder.neutralFocus) {
+ dialog.neutralButton.requestFocus();
+ }
+ if (builder.negativeFocus) {
+ dialog.negativeButton.requestFocus();
+ }
+
+ // Setup icon
+ if (builder.icon != null) {
+ dialog.icon.setVisibility(View.VISIBLE);
+ dialog.icon.setImageDrawable(builder.icon);
+ } else {
+ Drawable d = DialogUtils.resolveDrawable(builder.context, R.attr.md_icon);
+ if (d != null) {
+ dialog.icon.setVisibility(View.VISIBLE);
+ dialog.icon.setImageDrawable(d);
+ } else {
+ dialog.icon.setVisibility(View.GONE);
+ }
+ }
+
+ // Setup icon size limiting
+ int maxIconSize = builder.maxIconSize;
+ if (maxIconSize == -1) {
+ maxIconSize = DialogUtils.resolveDimension(builder.context, R.attr.md_icon_max_size);
+ }
+ if (builder.limitIconToDefaultSize || DialogUtils
+ .resolveBoolean(builder.context, R.attr.md_icon_limit_icon_to_default_size)) {
+ maxIconSize = builder.context.getResources().getDimensionPixelSize(R.dimen.md_icon_max_size);
+ }
+ if (maxIconSize > -1) {
+ dialog.icon.setAdjustViewBounds(true);
+ dialog.icon.setMaxHeight(maxIconSize);
+ dialog.icon.setMaxWidth(maxIconSize);
+ dialog.icon.requestLayout();
+ }
+
+ // Setup divider color in case content scrolls
+ if (!builder.dividerColorSet) {
+ final int dividerFallback = DialogUtils.resolveColor(dialog.getContext(), R.attr.md_divider);
+ builder.dividerColor = DialogUtils
+ .resolveColor(builder.context, R.attr.md_divider_color, dividerFallback);
+ }
+ dialog.view.setDividerColor(builder.dividerColor);
+
+ // Setup title and title frame
+ if (dialog.title != null) {
+ dialog.setTypeface(dialog.title, builder.mediumFont);
+ dialog.title.setTextColor(builder.titleColor);
+ dialog.title.setGravity(builder.titleGravity.getGravityInt());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ //noinspection ResourceType
+ dialog.title.setTextAlignment(builder.titleGravity.getTextAlignment());
+ }
+
+ if (builder.title == null) {
+ dialog.titleFrame.setVisibility(View.GONE);
+ } else {
+ dialog.title.setText(builder.title);
+ dialog.titleFrame.setVisibility(View.VISIBLE);
+ }
+ }
+
+ // Setup content
+ if (dialog.content != null) {
+ dialog.content.setMovementMethod(new LinkMovementMethod());
+ dialog.setTypeface(dialog.content, builder.regularFont);
+ dialog.content.setLineSpacing(0f, builder.contentLineSpacingMultiplier);
+ if (builder.linkColor == null) {
+ dialog.content.setLinkTextColor(
+ DialogUtils.resolveColor(dialog.getContext(), android.R.attr.textColorPrimary));
+ } else {
+ dialog.content.setLinkTextColor(builder.linkColor);
+ }
+ dialog.content.setTextColor(builder.contentColor);
+ dialog.content.setGravity(builder.contentGravity.getGravityInt());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ //noinspection ResourceType
+ dialog.content.setTextAlignment(builder.contentGravity.getTextAlignment());
+ }
+
+ if (builder.content != null) {
+ dialog.content.setText(builder.content);
+ dialog.content.setVisibility(View.VISIBLE);
+ } else {
+ dialog.content.setVisibility(View.GONE);
+ }
+ }
+
+ // Setup prompt checkbox
+ if (dialog.checkBoxPrompt != null) {
+ dialog.checkBoxPrompt.setText(builder.checkBoxPrompt);
+ dialog.checkBoxPrompt.setChecked(builder.checkBoxPromptInitiallyChecked);
+ dialog.checkBoxPrompt.setOnCheckedChangeListener(builder.checkBoxPromptListener);
+ dialog.setTypeface(dialog.checkBoxPrompt, builder.regularFont);
+ dialog.checkBoxPrompt.setTextColor(builder.contentColor);
+ MDTintHelper.setTint(dialog.checkBoxPrompt, builder.widgetColor);
+ }
+
+ // Setup action buttons
+ dialog.view.setButtonGravity(builder.buttonsGravity);
+ dialog.view.setButtonStackedGravity(builder.btnStackedGravity);
+ dialog.view.setStackingBehavior(builder.stackingBehavior);
+ boolean textAllCaps;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ textAllCaps = DialogUtils.resolveBoolean(builder.context, android.R.attr.textAllCaps, true);
+ if (textAllCaps) {
+ textAllCaps = DialogUtils.resolveBoolean(builder.context, R.attr.textAllCaps, true);
+ }
+ } else {
+ textAllCaps = DialogUtils.resolveBoolean(builder.context, R.attr.textAllCaps, true);
+ }
+
+ MDButton positiveTextView = dialog.positiveButton;
+ dialog.setTypeface(positiveTextView, builder.mediumFont);
+ positiveTextView.setAllCapsCompat(textAllCaps);
+ positiveTextView.setText(builder.positiveText);
+ positiveTextView.setTextColor(builder.positiveColor);
+ dialog.positiveButton.setStackedSelector(dialog.getButtonSelector(DialogAction.POSITIVE, true));
+ dialog.positiveButton
+ .setDefaultSelector(dialog.getButtonSelector(DialogAction.POSITIVE, false));
+ dialog.positiveButton.setTag(DialogAction.POSITIVE);
+ dialog.positiveButton.setOnClickListener(dialog);
+ dialog.positiveButton.setVisibility(View.VISIBLE);
+
+ MDButton negativeTextView = dialog.negativeButton;
+ dialog.setTypeface(negativeTextView, builder.mediumFont);
+ negativeTextView.setAllCapsCompat(textAllCaps);
+ negativeTextView.setText(builder.negativeText);
+ negativeTextView.setTextColor(builder.negativeColor);
+ dialog.negativeButton.setStackedSelector(dialog.getButtonSelector(DialogAction.NEGATIVE, true));
+ dialog.negativeButton
+ .setDefaultSelector(dialog.getButtonSelector(DialogAction.NEGATIVE, false));
+ dialog.negativeButton.setTag(DialogAction.NEGATIVE);
+ dialog.negativeButton.setOnClickListener(dialog);
+ dialog.negativeButton.setVisibility(View.VISIBLE);
+
+ MDButton neutralTextView = dialog.neutralButton;
+ dialog.setTypeface(neutralTextView, builder.mediumFont);
+ neutralTextView.setAllCapsCompat(textAllCaps);
+ neutralTextView.setText(builder.neutralText);
+ neutralTextView.setTextColor(builder.neutralColor);
+ dialog.neutralButton.setStackedSelector(dialog.getButtonSelector(DialogAction.NEUTRAL, true));
+ dialog.neutralButton.setDefaultSelector(dialog.getButtonSelector(DialogAction.NEUTRAL, false));
+ dialog.neutralButton.setTag(DialogAction.NEUTRAL);
+ dialog.neutralButton.setOnClickListener(dialog);
+ dialog.neutralButton.setVisibility(View.VISIBLE);
+
+ // Setup list dialog stuff
+ if (builder.listCallbackMultiChoice != null) {
+ dialog.selectedIndicesList = new ArrayList<>();
+ }
+ if (dialog.recyclerView != null) {
+ if (builder.adapter == null) {
+ // Determine list type
+ if (builder.listCallbackSingleChoice != null) {
+ dialog.listType = MaterialDialog.ListType.SINGLE;
+ } else if (builder.listCallbackMultiChoice != null) {
+ dialog.listType = MaterialDialog.ListType.MULTI;
+ if (builder.selectedIndices != null) {
+ dialog.selectedIndicesList = new ArrayList<>(Arrays.asList(builder.selectedIndices));
+ builder.selectedIndices = null;
+ }
+ } else {
+ dialog.listType = MaterialDialog.ListType.REGULAR;
+ }
+ builder.adapter = new DefaultRvAdapter(dialog,
+ MaterialDialog.ListType.getLayoutForType(dialog.listType));
+ } else if (builder.adapter instanceof MDAdapter) {
+ // Notify simple list adapter of the dialog it belongs to
+ ((MDAdapter) builder.adapter).setDialog(dialog);
+ }
+ }
+
+ // Setup progress dialog stuff if needed
+ setupProgressDialog(dialog);
+
+ // Setup input dialog stuff if needed
+ setupInputDialog(dialog);
+
+ // Setup custom views
+ if (builder.customView != null) {
+ ((MDRootLayout) dialog.view.findViewById(R.id.md_root)).noTitleNoPadding();
+ FrameLayout frame = (FrameLayout) dialog.view.findViewById(R.id.md_customViewFrame);
+ dialog.customViewFrame = frame;
+ View innerView = builder.customView;
+ if (innerView.getParent() != null) {
+ ((ViewGroup) innerView.getParent()).removeView(innerView);
+ }
+ if (builder.wrapCustomViewInScroll) {
+ /* Apply the frame padding to the content, this allows the ScrollView to draw it's
+ over scroll glow without clipping */
+ final Resources r = dialog.getContext().getResources();
+ final int framePadding = r.getDimensionPixelSize(R.dimen.md_dialog_frame_margin);
+ final ScrollView sv = new ScrollView(dialog.getContext());
+ int paddingTop = r.getDimensionPixelSize(R.dimen.md_content_padding_top);
+ int paddingBottom = r.getDimensionPixelSize(R.dimen.md_content_padding_bottom);
+ sv.setClipToPadding(false);
+ if (innerView instanceof EditText) {
+ // Setting padding to an EditText causes visual errors, set it to the parent instead
+ sv.setPadding(framePadding, paddingTop, framePadding, paddingBottom);
+ } else {
+ // Setting padding to scroll view pushes the scroll bars out, don't do it if not necessary (like above)
+ sv.setPadding(0, paddingTop, 0, paddingBottom);
+ innerView.setPadding(framePadding, 0, framePadding, 0);
+ }
+ sv.addView(innerView, new ScrollView.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ innerView = sv;
+ }
+ frame.addView(innerView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ }
+
+ // Setup user listeners
+ if (builder.showListener != null) {
+ dialog.setOnShowListener(builder.showListener);
+ }
+ if (builder.cancelListener != null) {
+ dialog.setOnCancelListener(builder.cancelListener);
+ }
+ if (builder.dismissListener != null) {
+ dialog.setOnDismissListener(builder.dismissListener);
+ }
+ if (builder.keyListener != null) {
+ dialog.setOnKeyListener(builder.keyListener);
+ }
+
+ // Setup internal show listener
+ dialog.setOnShowListenerInternal();
+
+ // Other internal initialization
+ dialog.invalidateList();
+ dialog.setViewInternal(dialog.view);
+ dialog.checkIfListInitScroll();
+
+ // Min height and max width calculations
+ WindowManager wm = dialog.getWindow().getWindowManager();
+ Display display = wm.getDefaultDisplay();
+ Point size = new Point();
+ display.getSize(size);
+ final int windowWidth = size.x;
+ final int windowHeight = size.y;
+
+ final int windowVerticalPadding = builder.context.getResources()
+ .getDimensionPixelSize(R.dimen.md_dialog_vertical_margin);
+ final int windowHorizontalPadding = builder.context.getResources()
+ .getDimensionPixelSize(R.dimen.md_dialog_horizontal_margin);
+ final int maxWidth = builder.context.getResources()
+ .getDimensionPixelSize(R.dimen.md_dialog_max_width);
+ final int calculatedWidth = windowWidth - (windowHorizontalPadding * 2);
+
+ dialog.view.setMaxHeight(windowHeight - windowVerticalPadding * 2);
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
+ lp.copyFrom(dialog.getWindow().getAttributes());
+ lp.width = Math.min(maxWidth, calculatedWidth);
+ dialog.getWindow().setAttributes(lp);
+ }
+
+ private static void fixCanvasScalingWhenHardwareAccelerated(ProgressBar pb) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ // Canvas scaling when hardware accelerated results in artifacts on older API levels, so
+ // we need to use software rendering
+ if (pb.isHardwareAccelerated() && pb.getLayerType() != View.LAYER_TYPE_SOFTWARE) {
+ pb.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+ }
+ }
+
+ private static void setupProgressDialog(final MaterialDialog dialog) {
+ final MaterialDialog.Builder builder = dialog.builder;
+ if (builder.indeterminateProgress || builder.progress > -2) {
+ dialog.progressBar = (ProgressBar) dialog.view.findViewById(android.R.id.progress);
+ if (dialog.progressBar == null) {
+ return;
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ if (builder.indeterminateProgress) {
+ if (builder.indeterminateIsHorizontalProgress) {
+ IndeterminateHorizontalProgressDrawable d = new IndeterminateHorizontalProgressDrawable(
+ builder.getContext());
+ d.setTint(builder.widgetColor);
+ dialog.progressBar.setProgressDrawable(d);
+ dialog.progressBar.setIndeterminateDrawable(d);
+ } else {
+ IndeterminateProgressDrawable d = new IndeterminateProgressDrawable(
+ builder.getContext());
+ d.setTint(builder.widgetColor);
+ dialog.progressBar.setProgressDrawable(d);
+ dialog.progressBar.setIndeterminateDrawable(d);
+ }
+ } else {
+ HorizontalProgressDrawable d = new HorizontalProgressDrawable(builder.getContext());
+ d.setTint(builder.widgetColor);
+ dialog.progressBar.setProgressDrawable(d);
+ dialog.progressBar.setIndeterminateDrawable(d);
+ }
+ } else {
+ MDTintHelper.setTint(dialog.progressBar, builder.widgetColor);
+ }
+
+ if (!builder.indeterminateProgress || builder.indeterminateIsHorizontalProgress) {
+ dialog.progressBar.setIndeterminate(
+ builder.indeterminateProgress && builder.indeterminateIsHorizontalProgress);
+ dialog.progressBar.setProgress(0);
+ dialog.progressBar.setMax(builder.progressMax);
+ dialog.progressLabel = (TextView) dialog.view.findViewById(R.id.md_label);
+ if (dialog.progressLabel != null) {
+ dialog.progressLabel.setTextColor(builder.contentColor);
+ dialog.setTypeface(dialog.progressLabel, builder.mediumFont);
+ dialog.progressLabel.setText(builder.progressPercentFormat.format(0));
+ }
+ dialog.progressMinMax = (TextView) dialog.view.findViewById(R.id.md_minMax);
+ if (dialog.progressMinMax != null) {
+ dialog.progressMinMax.setTextColor(builder.contentColor);
+ dialog.setTypeface(dialog.progressMinMax, builder.regularFont);
+
+ if (builder.showMinMax) {
+ dialog.progressMinMax.setVisibility(View.VISIBLE);
+ dialog.progressMinMax.setText(String.format(builder.progressNumberFormat,
+ 0, builder.progressMax));
+ ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) dialog.progressBar
+ .getLayoutParams();
+ lp.leftMargin = 0;
+ lp.rightMargin = 0;
+ } else {
+ dialog.progressMinMax.setVisibility(View.GONE);
+ }
+ } else {
+ builder.showMinMax = false;
+ }
+ }
+ }
+
+ if (dialog.progressBar != null) {
+ fixCanvasScalingWhenHardwareAccelerated(dialog.progressBar);
+ }
+ }
+
+ private static void setupInputDialog(final MaterialDialog dialog) {
+ final MaterialDialog.Builder builder = dialog.builder;
+ dialog.input = (EditText) dialog.view.findViewById(android.R.id.input);
+ if (dialog.input == null) {
+ return;
+ }
+ dialog.setTypeface(dialog.input, builder.regularFont);
+ if (builder.inputPrefill != null) {
+ dialog.input.setText(builder.inputPrefill);
+ }
+ dialog.setInternalInputCallback();
+ dialog.input.setHint(builder.inputHint);
+ dialog.input.setSingleLine();
+ dialog.input.setTextColor(builder.contentColor);
+ dialog.input.setHintTextColor(DialogUtils.adjustAlpha(builder.contentColor, 0.3f));
+ MDTintHelper.setTint(dialog.input, dialog.builder.widgetColor);
+
+ if (builder.inputType != -1) {
+ dialog.input.setInputType(builder.inputType);
+ if (builder.inputType != InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD &&
+ (builder.inputType & InputType.TYPE_TEXT_VARIATION_PASSWORD)
+ == InputType.TYPE_TEXT_VARIATION_PASSWORD) {
+ // If the flags contain TYPE_TEXT_VARIATION_PASSWORD, apply the password transformation method automatically
+ dialog.input.setTransformationMethod(PasswordTransformationMethod.getInstance());
+ }
+ }
+
+ dialog.inputMinMax = (TextView) dialog.view.findViewById(R.id.md_minMax);
+ if (builder.inputMinLength > 0 || builder.inputMaxLength > -1) {
+ dialog.invalidateInputMinMaxIndicator(dialog.input.getText().toString().length(),
+ !builder.inputAllowEmpty);
+ } else {
+ dialog.inputMinMax.setVisibility(View.GONE);
+ dialog.inputMinMax = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/GravityEnum.java b/widget/src/main/java/com/hzecool/widget/materialdialog/GravityEnum.java
new file mode 100644
index 0000000..f4c1846
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/GravityEnum.java
@@ -0,0 +1,40 @@
+package com.hzecool.widget.materialdialog;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.view.Gravity;
+import android.view.View;
+
+public enum GravityEnum {
+ START, CENTER, END;
+
+ private static final boolean HAS_RTL =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
+
+ @SuppressLint("RtlHardcoded")
+ public int getGravityInt() {
+ switch (this) {
+ case START:
+ return HAS_RTL ? Gravity.START : Gravity.LEFT;
+ case CENTER:
+ return Gravity.CENTER_HORIZONTAL;
+ case END:
+ return HAS_RTL ? Gravity.END : Gravity.RIGHT;
+ default:
+ throw new IllegalStateException("Invalid gravity constant");
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ public int getTextAlignment() {
+ switch (this) {
+ case CENTER:
+ return View.TEXT_ALIGNMENT_CENTER;
+ case END:
+ return View.TEXT_ALIGNMENT_VIEW_END;
+ default:
+ return View.TEXT_ALIGNMENT_VIEW_START;
+ }
+ }
+}
\ No newline at end of file
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/MaterialDialog.java b/widget/src/main/java/com/hzecool/widget/materialdialog/MaterialDialog.java
new file mode 100644
index 0000000..f4cb88d
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/MaterialDialog.java
@@ -0,0 +1,2270 @@
+package com.hzecool.widget.materialdialog;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.ColorStateList;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Handler;
+import android.support.annotation.ArrayRes;
+import android.support.annotation.AttrRes;
+import android.support.annotation.ColorInt;
+import android.support.annotation.ColorRes;
+import android.support.annotation.DimenRes;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IntRange;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
+import android.support.annotation.UiThread;
+import android.support.v4.content.res.ResourcesCompat;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.text.Editable;
+import android.text.Html;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.method.DigitsKeyListener;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+import com.hzecool.widget.R;
+import com.hzecool.widget.materialdialog.internal.MDButton;
+import com.hzecool.widget.materialdialog.internal.MDRootLayout;
+import com.hzecool.widget.materialdialog.internal.MDTintHelper;
+import com.hzecool.widget.materialdialog.internal.ThemeSingleton;
+import com.hzecool.widget.materialdialog.util.DialogUtils;
+import com.hzecool.widget.materialdialog.util.RippleHelper;
+import com.hzecool.widget.materialdialog.util.TypefaceHelper;
+
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * @author Aidan Follestad (afollestad)
+ */
+public class MaterialDialog extends DialogBase implements
+ View.OnClickListener, DefaultRvAdapter.InternalListCallback {
+
+ protected final Builder builder;
+ private final Handler handler;
+ protected ImageView icon;
+ protected TextView title;
+ protected TextView content;
+
+ EditText input;
+ RecyclerView recyclerView;
+ View titleFrame;
+ FrameLayout customViewFrame;
+ ProgressBar progressBar;
+ TextView progressLabel;
+ TextView progressMinMax;
+ TextView inputMinMax;
+ CheckBox checkBoxPrompt;
+ MDButton positiveButton;
+ MDButton neutralButton;
+ MDButton negativeButton;
+ ListType listType;
+ List selectedIndicesList;
+
+ @SuppressLint("InflateParams")
+ protected MaterialDialog(Builder builder) {
+ super(builder.context, DialogInit.getTheme(builder));
+ handler = new Handler();
+ this.builder = builder;
+ final LayoutInflater inflater = LayoutInflater.from(builder.context);
+ view = (MDRootLayout) inflater.inflate(DialogInit.getInflateLayout(builder), null);
+ DialogInit.init(this);
+ }
+
+ public final Builder getBuilder() {
+ return builder;
+ }
+
+ public final void setTypeface(TextView target, Typeface t) {
+ if (t == null) {
+ return;
+ }
+ int flags = target.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG;
+ target.setPaintFlags(flags);
+ target.setTypeface(t);
+ }
+
+ @SuppressWarnings("unused")
+ @Nullable
+ public Object getTag() {
+ return builder.tag;
+ }
+
+ final void checkIfListInitScroll() {
+ if (recyclerView == null) {
+ return;
+ }
+ recyclerView.getViewTreeObserver()
+ .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @SuppressWarnings("ConstantConditions")
+ @Override
+ public void onGlobalLayout() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+ //noinspection deprecation
+ recyclerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ } else {
+ recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ }
+
+ if (listType == ListType.SINGLE || listType == ListType.MULTI) {
+ int selectedIndex;
+ if (listType == ListType.SINGLE) {
+ if (builder.selectedIndex < 0) {
+ return;
+ }
+ selectedIndex = builder.selectedIndex;
+ } else {
+ if (selectedIndicesList == null || selectedIndicesList.size() == 0) {
+ return;
+ }
+ Collections.sort(selectedIndicesList);
+ selectedIndex = selectedIndicesList.get(0);
+ }
+
+ final int fSelectedIndex = selectedIndex;
+ recyclerView.post(new Runnable() {
+ @Override
+ public void run() {
+ recyclerView.requestFocus();
+ builder.layoutManager.scrollToPosition(fSelectedIndex);
+ }
+ });
+ }
+ }
+ });
+ }
+
+ /**
+ * Sets the dialog RecyclerView's adapter/layout manager, and it's item click listener.
+ */
+ final void invalidateList() {
+ if (recyclerView == null) {
+ return;
+ } else if ((builder.items == null ||
+ builder.items.size() == 0) && builder.adapter == null) {
+ return;
+ }
+ if (builder.layoutManager == null) {
+ builder.layoutManager = new LinearLayoutManager(getContext());
+ }
+ recyclerView.setLayoutManager(builder.layoutManager);
+ recyclerView.setAdapter(builder.adapter);
+ if (listType != null) {
+ ((DefaultRvAdapter) builder.adapter).setCallback(this);
+ }
+ }
+
+ @Override
+ public boolean onItemSelected(MaterialDialog dialog, View view,
+ int position, CharSequence text, boolean longPress) {
+ if (!view.isEnabled()) {
+ return false;
+ }
+ if (listType == null || listType == ListType.REGULAR) {
+ // Default adapter, non choice mode
+ if (builder.autoDismiss) {
+ // If auto dismiss is enabled, dismiss the dialog when a list item is selected
+ dismiss();
+ }
+ if (!longPress && builder.listCallback != null) {
+ builder.listCallback.onSelection(this, view, position,
+ builder.items.get(position));
+ }
+ if (longPress && builder.listLongCallback != null) {
+ return builder.listLongCallback.onLongSelection(this, view,
+ position, builder.items.get(position));
+ }
+ } else {
+ // Default adapter, choice mode
+ if (listType == ListType.MULTI) {
+ final CheckBox cb = (CheckBox) view.findViewById(R.id.md_control);
+ if (!cb.isEnabled()) {
+ return false;
+ }
+ final boolean shouldBeChecked = !selectedIndicesList.contains(position);
+ if (shouldBeChecked) {
+ // Add the selection to the states first so the callback includes it (when alwaysCallMultiChoiceCallback)
+ selectedIndicesList.add(position);
+ if (builder.alwaysCallMultiChoiceCallback) {
+ // If the checkbox wasn't previously selected, and the callback returns true, add it to the states and check it
+ if (sendMultiChoiceCallback()) {
+ cb.setChecked(true);
+ } else {
+ // The callback cancelled selection, remove it from the states
+ selectedIndicesList.remove(Integer.valueOf(position));
+ }
+ } else {
+ // The callback was not used to check if selection is allowed, just select it
+ cb.setChecked(true);
+ }
+ } else {
+ // Remove the selection from the states first so the callback does not include it (when alwaysCallMultiChoiceCallback)
+ selectedIndicesList.remove(Integer.valueOf(position));
+ if (builder.alwaysCallMultiChoiceCallback) {
+ // If the checkbox was previously selected, and the callback returns true, remove it from the states and uncheck it
+ if (sendMultiChoiceCallback()) {
+ cb.setChecked(false);
+ } else {
+ // The callback cancelled unselection, re-add it to the states
+ selectedIndicesList.add(position);
+ }
+ } else {
+ // The callback was not used to check if the unselection is allowed, just uncheck it
+ cb.setChecked(false);
+ }
+ }
+ } else if (listType == ListType.SINGLE) {
+ final RadioButton radio = (RadioButton) view.findViewById(R.id.md_control);
+ if (!radio.isEnabled()) {
+ return false;
+ }
+ boolean allowSelection = true;
+ final int oldSelected = builder.selectedIndex;
+
+ if (builder.autoDismiss && builder.positiveText == null) {
+ // If auto dismiss is enabled, and no action button is visible to approve the selection, dismiss the dialog
+ dismiss();
+ // Don't allow the selection to be updated since the dialog is being dismissed anyways
+ allowSelection = false;
+ // Update selected index and send callback
+ builder.selectedIndex = position;
+ sendSingleChoiceCallback(view);
+ } else if (builder.alwaysCallSingleChoiceCallback) {
+ // Temporarily set the new index so the callback uses the right one
+ builder.selectedIndex = position;
+ // Only allow the radio button to be checked if the callback returns true
+ allowSelection = sendSingleChoiceCallback(view);
+ // Restore the old selected index, so the state is updated below
+ builder.selectedIndex = oldSelected;
+ }
+ // Update the checked states
+ if (allowSelection) {
+ builder.selectedIndex = position;
+ radio.setChecked(true);
+ builder.adapter.notifyItemChanged(oldSelected);
+ builder.adapter.notifyItemChanged(position);
+ }
+ }
+ }
+ return true;
+ }
+
+ final Drawable getListSelector() {
+ if (builder.listSelector != 0) {
+ return ResourcesCompat.getDrawable(builder.context.getResources(),
+ builder.listSelector, null);
+ }
+ final Drawable d = DialogUtils.resolveDrawable(builder.context,
+ R.attr.md_list_selector);
+ if (d != null) {
+ return d;
+ }
+ return DialogUtils.resolveDrawable(getContext(), R.attr.md_list_selector);
+ }
+
+ public RecyclerView getRecyclerView() {
+ return recyclerView;
+ }
+
+ public boolean isPromptCheckBoxChecked() {
+ return checkBoxPrompt != null && checkBoxPrompt.isChecked();
+ }
+
+ @SuppressWarnings("unused")
+ public void setPromptCheckBoxChecked(boolean checked) {
+ if (checkBoxPrompt != null) {
+ checkBoxPrompt.setChecked(checked);
+ }
+ }
+
+ /* package */ Drawable getButtonSelector(DialogAction which, boolean isStacked) {
+ if (isStacked) {
+ if (builder.btnSelectorStacked != 0) {
+ return ResourcesCompat.getDrawable(builder.context.getResources(),
+ builder.btnSelectorStacked, null);
+ }
+ final Drawable d = DialogUtils.resolveDrawable(builder.context,
+ R.attr.md_btn_stacked_selector);
+ if (d != null) {
+ return d;
+ }
+ return DialogUtils.resolveDrawable(getContext(), R.attr.md_btn_stacked_selector);
+ } else {
+ switch (which) {
+ default: {
+ if (builder.btnSelectorPositive != 0) {
+ return ResourcesCompat.getDrawable(builder.context.getResources(),
+ builder.btnSelectorPositive, null);
+ }
+ Drawable d = DialogUtils.resolveDrawable(builder.context,
+ R.attr.md_btn_positive_selector);
+ if (d != null) {
+ return d;
+ }
+ d = DialogUtils.resolveDrawable(getContext(),
+ R.attr.md_btn_positive_selector);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ RippleHelper.applyColor(d, builder.buttonRippleColor);
+ }
+ return d;
+ }
+ case NEUTRAL: {
+ if (builder.btnSelectorNeutral != 0) {
+ return ResourcesCompat.getDrawable(builder.context.getResources(),
+ builder.btnSelectorNeutral, null);
+ }
+ Drawable d = DialogUtils.resolveDrawable(builder.context,
+ R.attr.md_btn_neutral_selector);
+ if (d != null) {
+ return d;
+ }
+ d = DialogUtils.resolveDrawable(getContext(), R.attr.md_btn_neutral_selector);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ RippleHelper.applyColor(d, builder.buttonRippleColor);
+ }
+ return d;
+ }
+ case NEGATIVE: {
+ if (builder.btnSelectorNegative != 0) {
+ return ResourcesCompat.getDrawable(builder.context.getResources(),
+ builder.btnSelectorNegative, null);
+ }
+ Drawable d = DialogUtils.resolveDrawable(builder.context,
+ R.attr.md_btn_negative_selector);
+ if (d != null) {
+ return d;
+ }
+ d = DialogUtils.resolveDrawable(getContext(),
+ R.attr.md_btn_negative_selector);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ RippleHelper.applyColor(d, builder.buttonRippleColor);
+ }
+ return d;
+ }
+ }
+ }
+ }
+
+ private boolean sendSingleChoiceCallback(View v) {
+ if (builder.listCallbackSingleChoice == null) {
+ return false;
+ }
+ CharSequence text = null;
+ if (builder.selectedIndex >= 0 && builder.selectedIndex < builder.items.size()) {
+ text = builder.items.get(builder.selectedIndex);
+ }
+ return builder.listCallbackSingleChoice.onSelection(this, v, builder.selectedIndex, text);
+ }
+
+ private boolean sendMultiChoiceCallback() {
+ if (builder.listCallbackMultiChoice == null) {
+ return false;
+ }
+ Collections.sort(selectedIndicesList); // make sure the indices are in order
+ List selectedTitles = new ArrayList<>();
+ for (Integer i : selectedIndicesList) {
+ if (i < 0 || i > builder.items.size() - 1) {
+ continue;
+ }
+ selectedTitles.add(builder.items.get(i));
+ }
+ return builder.listCallbackMultiChoice.onSelection(this,
+ selectedIndicesList.toArray(new Integer[selectedIndicesList.size()]),
+ selectedTitles.toArray(new CharSequence[selectedTitles.size()]));
+ }
+
+ @Override
+ public final void onClick(View v) {
+ DialogAction tag = (DialogAction) v.getTag();
+ switch (tag) {
+ case POSITIVE: {
+ if (builder.callback != null) {
+ builder.callback.onAny(this);
+ builder.callback.onPositive(this);
+ }
+ if (builder.onPositiveCallback != null) {
+ builder.onPositiveCallback.onClick(this, tag);
+ }
+ if (!builder.alwaysCallSingleChoiceCallback) {
+ sendSingleChoiceCallback(v);
+ }
+ if (!builder.alwaysCallMultiChoiceCallback) {
+ sendMultiChoiceCallback();
+ }
+ if (builder.inputCallback != null && input != null &&
+ !builder.alwaysCallInputCallback) {
+ builder.inputCallback.onInput(this, input.getText());
+ }
+ if (builder.autoDismiss) {
+ dismiss();
+ }
+ break;
+ }
+ case NEGATIVE: {
+ if (builder.callback != null) {
+ builder.callback.onAny(this);
+ builder.callback.onNegative(this);
+ }
+ if (builder.onNegativeCallback != null) {
+ builder.onNegativeCallback.onClick(this, tag);
+ }
+ if (builder.autoDismiss) {
+ cancel();
+ }
+ break;
+ }
+ case NEUTRAL: {
+ if (builder.callback != null) {
+ builder.callback.onAny(this);
+ builder.callback.onNeutral(this);
+ }
+ if (builder.onNeutralCallback != null) {
+ builder.onNeutralCallback.onClick(this, tag);
+ }
+ if (builder.autoDismiss) {
+ dismiss();
+ }
+ break;
+ }
+ }
+ if (builder.onAnyCallback != null) {
+ builder.onAnyCallback.onClick(this, tag);
+ }
+ }
+
+ @Override
+ @UiThread
+ public void show() {
+ try {
+ super.show();
+ } catch (WindowManager.BadTokenException e) {
+ throw new DialogException("Bad window token, you cannot show a dialog " +
+ "before an Activity is created or after it's hidden.");
+ }
+ }
+
+ /**
+ * Retrieves the view of an action button, allowing you to modify properties such as whether or
+ * not it's enabled. Use {@link #setActionButton(DialogAction, int)} to change text, since the
+ * view returned here is not the view that displays text.
+ *
+ * @param which The action button of which to get the view for.
+ * @return The view from the dialog's layout representing this action button.
+ */
+ public final MDButton getActionButton(@NonNull DialogAction which) {
+ switch (which) {
+ default:
+ return positiveButton;
+ case NEUTRAL:
+ return neutralButton;
+ case NEGATIVE:
+ return negativeButton;
+ }
+ }
+
+ /**
+ * Retrieves the view representing the dialog as a whole. Be careful with this.
+ */
+ public final View getView() {
+ return view;
+ }
+
+ @Nullable
+ public final EditText getInputEditText() {
+ return input;
+ }
+
+ /**
+ * Retrieves the TextView that contains the dialog title. If you want to update the title, use
+ * #{@link #setTitle(CharSequence)} instead.
+ */
+ @SuppressWarnings("unused")
+ public final TextView getTitleView() {
+ return title;
+ }
+
+ /**
+ * Retrieves the ImageView that contains the dialog icon.
+ */
+ @SuppressWarnings("unused")
+ public ImageView getIconView() {
+ return icon;
+ }
+
+ /**
+ * Retrieves the TextView that contains the dialog content. If you want to update the content
+ * (message), use #{@link #setContent(CharSequence)} instead.
+ */
+ @Nullable
+ @SuppressWarnings("unused")
+ public final TextView getContentView() {
+ return content;
+ }
+
+ /**
+ * Retrieves the custom view that was inflated or set to the MaterialDialog during building.
+ *
+ * @return The custom view that was passed into the Builder.
+ */
+ @Nullable
+ public final View getCustomView() {
+ return builder.customView;
+ }
+
+ /**
+ * Updates an action button's title, causing invalidation to check if the action buttons should
+ * be stacked. Setting an action button's text to null is a shortcut for hiding it, too.
+ *
+ * @param which The action button to update.
+ * @param title The new title of the action button.
+ */
+ @SuppressWarnings("WeakerAccess")
+ @UiThread
+ public final void setActionButton(@NonNull final DialogAction which, final CharSequence title) {
+ switch (which) {
+ default:
+ builder.positiveText = title;
+ positiveButton.setText(title);
+ positiveButton.setVisibility(title == null ? View.GONE : View.VISIBLE);
+ break;
+ case NEUTRAL:
+ builder.neutralText = title;
+ neutralButton.setText(title);
+ neutralButton.setVisibility(title == null ? View.GONE : View.VISIBLE);
+ break;
+ case NEGATIVE:
+ builder.negativeText = title;
+ negativeButton.setText(title);
+ negativeButton.setVisibility(title == null ? View.GONE : View.VISIBLE);
+ break;
+ }
+ }
+
+ /**
+ * Updates an action button's title, causing invalidation to check if the action buttons should
+ * be stacked.
+ *
+ * @param which The action button to update.
+ * @param titleRes The string resource of the new title of the action button.
+ */
+ public final void setActionButton(DialogAction which, @StringRes int titleRes) {
+ setActionButton(which, getContext().getText(titleRes));
+ }
+
+ /**
+ * Gets whether or not the positive, neutral, or negative action button is visible.
+ *
+ * @return Whether or not 1 or more action buttons is visible.
+ */
+ public final boolean hasActionButtons() {
+ return numberOfActionButtons() > 0;
+ }
+
+ /**
+ * Gets the number of visible action buttons.
+ *
+ * @return 0 through 3, depending on how many should be or are visible.
+ */
+ @SuppressWarnings("WeakerAccess")
+ public final int numberOfActionButtons() {
+ int number = 0;
+ if (builder.positiveText != null && positiveButton.getVisibility() == View.VISIBLE) {
+ number++;
+ }
+ if (builder.neutralText != null && neutralButton.getVisibility() == View.VISIBLE) {
+ number++;
+ }
+ if (builder.negativeText != null && negativeButton.getVisibility() == View.VISIBLE) {
+ number++;
+ }
+ return number;
+ }
+
+ @UiThread
+ @Override
+ public final void setTitle(CharSequence newTitle) {
+ title.setText(newTitle);
+ }
+
+ @UiThread
+ @Override
+ public final void setTitle(@StringRes int newTitleRes) {
+ setTitle(builder.context.getString(newTitleRes));
+ }
+
+ @SuppressWarnings("unused")
+ @UiThread
+ public final void setTitle(@StringRes int newTitleRes, @Nullable Object... formatArgs) {
+ setTitle(builder.context.getString(newTitleRes, formatArgs));
+ }
+
+ @UiThread
+ public void setIcon(@DrawableRes final int resId) {
+ icon.setImageResource(resId);
+ icon.setVisibility(resId != 0 ? View.VISIBLE : View.GONE);
+ }
+
+ @UiThread
+ public void setIcon(final Drawable d) {
+ icon.setImageDrawable(d);
+ icon.setVisibility(d != null ? View.VISIBLE : View.GONE);
+ }
+
+ @SuppressWarnings("unused")
+ @UiThread
+ public void setIconAttribute(@AttrRes int attrId) {
+ Drawable d = DialogUtils.resolveDrawable(builder.context, attrId);
+ setIcon(d);
+ }
+
+ @UiThread
+ public void setDigitsListener(DigitsKeyListener digitsListener){
+ input.setKeyListener(digitsListener);
+ }
+
+ @UiThread
+ public final void setContent(CharSequence newContent) {
+ content.setText(newContent);
+ content.setVisibility(TextUtils.isEmpty(newContent) ? View.GONE : View.VISIBLE);
+ }
+
+ @UiThread
+ public final void setContent(@StringRes int newContentRes) {
+ setContent(builder.context.getString(newContentRes));
+ }
+
+ @SuppressWarnings("unused")
+ @UiThread
+ public final void setContent(@StringRes int newContentRes, @Nullable Object... formatArgs) {
+ setContent(builder.context.getString(newContentRes, formatArgs));
+ }
+
+ @Nullable
+ public final ArrayList getItems() {
+ return builder.items;
+ }
+
+ @UiThread
+ public final void setItems(CharSequence... items) {
+ if (builder.adapter == null) {
+ throw new IllegalStateException("This MaterialDialog instance does not " +
+ "yet have an adapter set to it. You cannot use setItems().");
+ }
+ if (items != null) {
+ builder.items = new ArrayList<>(items.length);
+ Collections.addAll(builder.items, items);
+ } else {
+ builder.items = null;
+ }
+ if (!(builder.adapter instanceof DefaultRvAdapter)) {
+ throw new IllegalStateException("When using a custom adapter, setItems() " +
+ "cannot be used. Set items through the adapter instead.");
+ }
+ notifyItemsChanged();
+ }
+
+ @UiThread
+ public final void notifyItemInserted(
+ @IntRange(from = 0, to = Integer.MAX_VALUE) int index) {
+ builder.adapter.notifyItemInserted(index);
+ }
+
+ @SuppressWarnings("unused")
+ @UiThread
+ public final void notifyItemChanged(
+ @IntRange(from = 0, to = Integer.MAX_VALUE) int index) {
+ builder.adapter.notifyItemChanged(index);
+ }
+
+ @UiThread
+ public final void notifyItemsChanged() {
+ builder.adapter.notifyDataSetChanged();
+ }
+
+ public final int getCurrentProgress() {
+ if (progressBar == null) {
+ return -1;
+ }
+ return progressBar.getProgress();
+ }
+
+ @SuppressWarnings("unused")
+ public ProgressBar getProgressBar() {
+ return progressBar;
+ }
+
+ public final void incrementProgress(final int by) {
+ setProgress(getCurrentProgress() + by);
+ }
+
+ public final void setProgress(final int progress) {
+ if (builder.progress <= -2) {
+ Log.w("MaterialDialog", "Calling setProgress(int) on an " +
+ "indeterminate progress dialog has no effect!");
+ return;
+ }
+ progressBar.setProgress(progress);
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (progressLabel != null) {
+ progressLabel.setText(builder.progressPercentFormat.format(
+ (float) getCurrentProgress() / (float) getMaxProgress()));
+ }
+ if (progressMinMax != null) {
+ progressMinMax.setText(String.format(builder.progressNumberFormat,
+ getCurrentProgress(), getMaxProgress()));
+ }
+ }
+ });
+ }
+
+ @SuppressWarnings("unused")
+ public final boolean isIndeterminateProgress() {
+ return builder.indeterminateProgress;
+ }
+
+ public final int getMaxProgress() {
+ if (progressBar == null) {
+ return -1;
+ }
+ return progressBar.getMax();
+ }
+
+ @SuppressWarnings("unused")
+ public final void setMaxProgress(final int max) {
+ if (builder.progress <= -2) {
+ throw new IllegalStateException("Cannot use setMaxProgress() on this dialog.");
+ }
+ progressBar.setMax(max);
+ }
+
+ /**
+ * Change the format of the small text showing the percentage of progress. The default is
+ * NumberFormat.getPercentageInstance().
+ */
+ @SuppressWarnings("unused")
+ public final void setProgressPercentFormat(NumberFormat format) {
+ builder.progressPercentFormat = format;
+ setProgress(getCurrentProgress()); // invalidates display
+ }
+
+ /**
+ * Change the format of the small text showing current and maximum units of progress. The
+ * default is "%1d/%2d".
+ */
+ @SuppressWarnings("unused")
+ public final void setProgressNumberFormat(String format) {
+ builder.progressNumberFormat = format;
+ setProgress(getCurrentProgress()); // invalidates display
+ }
+
+ public final boolean isCancelled() {
+ return !isShowing();
+ }
+
+ /**
+ * Convenience method for getting the currently selected index of a single choice list.
+ *
+ * @return Currently selected index of a single choice list, or -1 if not showing a single choice
+ * list
+ */
+ @SuppressWarnings("unused")
+ public int getSelectedIndex() {
+ if (builder.listCallbackSingleChoice != null) {
+ return builder.selectedIndex;
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Convenience method for setting the currently selected index of a single choice list. This
+ * only works if you are not using a custom adapter; if you're using a custom adapter, an
+ * IllegalStateException is thrown. Note that this does not call the respective single choice
+ * callback.
+ *
+ * @param index The index of the list item to check.
+ */
+ @UiThread
+ @SuppressWarnings("unused")
+ public void setSelectedIndex(int index) {
+ builder.selectedIndex = index;
+ if (builder.adapter != null && builder.adapter instanceof DefaultRvAdapter) {
+ builder.adapter.notifyDataSetChanged();
+ } else {
+ throw new IllegalStateException("You can only use setSelectedIndex() " +
+ "with the default adapter implementation.");
+ }
+ }
+
+ /**
+ * Convenience method for getting the currently selected indices of a multi choice list
+ *
+ * @return Currently selected index of a multi choice list, or null if not showing a multi choice
+ * list
+ */
+ @Nullable
+ @SuppressWarnings("unused")
+ public Integer[] getSelectedIndices() {
+ if (builder.listCallbackMultiChoice != null) {
+ return selectedIndicesList.toArray(new Integer[selectedIndicesList.size()]);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Convenience method for setting the currently selected indices of a multi choice list. This
+ * only works if you are not using a custom adapter; if you're using a custom adapter, an
+ * IllegalStateException is thrown. Note that this does not call the respective multi choice
+ * callback.
+ *
+ * @param indices The indices of the list items to check.
+ */
+ @UiThread
+ @SuppressWarnings("unused")
+ public void setSelectedIndices(@NonNull Integer[] indices) {
+ selectedIndicesList = new ArrayList<>(Arrays.asList(indices));
+ if (builder.adapter != null && builder.adapter instanceof DefaultRvAdapter) {
+ builder.adapter.notifyDataSetChanged();
+ } else {
+ throw new IllegalStateException("You can only use setSelectedIndices() " +
+ "with the default adapter implementation.");
+ }
+ }
+
+ /**
+ * Clears all selected checkboxes from multi choice list dialogs.
+ */
+ public void clearSelectedIndices() {
+ clearSelectedIndices(true);
+ }
+
+ /**
+ * Clears all selected checkboxes from multi choice list dialogs.
+ *
+ * @param sendCallback Defaults to true. True will notify the multi-choice callback, if any.
+ */
+ @SuppressWarnings("WeakerAccess")
+ public void clearSelectedIndices(boolean sendCallback) {
+ if (listType == null || listType != ListType.MULTI) {
+ throw new IllegalStateException("You can only use clearSelectedIndices() " +
+ "with multi choice list dialogs.");
+ }
+ if (builder.adapter != null && builder.adapter instanceof DefaultRvAdapter) {
+ if (selectedIndicesList != null) {
+ selectedIndicesList.clear();
+ }
+ builder.adapter.notifyDataSetChanged();
+ if (sendCallback && builder.listCallbackMultiChoice != null) {
+ sendMultiChoiceCallback();
+ }
+ } else {
+ throw new IllegalStateException("You can only use clearSelectedIndices() " +
+ "with the default adapter implementation.");
+ }
+ }
+
+ /**
+ * Selects all checkboxes in multi choice list dialogs.
+ */
+ @SuppressWarnings("unused")
+ public void selectAllIndices() {
+ selectAllIndices(true);
+ }
+
+ /**
+ * Selects all checkboxes in multi choice list dialogs.
+ *
+ * @param sendCallback Defaults to true. True will notify the multi-choice callback, if any.
+ */
+ @SuppressWarnings("WeakerAccess")
+ public void selectAllIndices(boolean sendCallback) {
+ if (listType == null || listType != ListType.MULTI) {
+ throw new IllegalStateException("You can only use selectAllIndices() with " +
+ "multi choice list dialogs.");
+ }
+ if (builder.adapter != null && builder.adapter instanceof DefaultRvAdapter) {
+ if (selectedIndicesList == null) {
+ selectedIndicesList = new ArrayList<>();
+ }
+ for (int i = 0; i < builder.adapter.getItemCount(); i++) {
+ if (!selectedIndicesList.contains(i)) {
+ selectedIndicesList.add(i);
+ }
+ }
+ builder.adapter.notifyDataSetChanged();
+ if (sendCallback && builder.listCallbackMultiChoice != null) {
+ sendMultiChoiceCallback();
+ }
+ } else {
+ throw new IllegalStateException("You can only use selectAllIndices() with the " +
+ "default adapter implementation.");
+ }
+ }
+
+ @Override
+ public final void onShow(DialogInterface dialog) {
+ if (input != null) {
+ DialogUtils.showKeyboard(this, builder);
+ if (input.getText().length() > 0) {
+ input.setSelection(input.getText().length());
+ }
+ }
+ super.onShow(dialog);
+ }
+
+ void setInternalInputCallback() {
+ if (input == null) {
+ return;
+ }
+ input.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ final int length = s.toString().length();
+ boolean emptyDisabled = false;
+ if (!builder.inputAllowEmpty) {
+ emptyDisabled = length == 0;
+ final View positiveAb = getActionButton(DialogAction.POSITIVE);
+ positiveAb.setEnabled(!emptyDisabled);
+ }
+ invalidateInputMinMaxIndicator(length, emptyDisabled);
+ if (builder.alwaysCallInputCallback) {
+ builder.inputCallback.onInput(MaterialDialog.this, s);
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+ });
+ }
+
+ void invalidateInputMinMaxIndicator(int currentLength, boolean emptyDisabled) {
+ if (inputMinMax != null) {
+ if (builder.inputMaxLength > 0) {
+ inputMinMax.setText(
+ String.format(Locale.getDefault(), "%d/%d", currentLength, builder.inputMaxLength));
+ inputMinMax.setVisibility(View.VISIBLE);
+ } else {
+ inputMinMax.setVisibility(View.GONE);
+ }
+ final boolean isDisabled = (emptyDisabled && currentLength == 0) ||
+ (builder.inputMaxLength > 0 && currentLength > builder.inputMaxLength) ||
+ currentLength < builder.inputMinLength;
+ final int colorText = isDisabled ? builder.inputRangeErrorColor : builder.contentColor;
+ final int colorWidget = isDisabled ? builder.inputRangeErrorColor : builder.widgetColor;
+ if (builder.inputMaxLength > 0) {
+ inputMinMax.setTextColor(colorText);
+ }
+ MDTintHelper.setTint(input, colorWidget);
+ final View positiveAb = getActionButton(DialogAction.POSITIVE);
+ positiveAb.setEnabled(!isDisabled);
+ }
+ }
+
+ @Override
+ public void dismiss() {
+ if (input != null) {
+ DialogUtils.hideKeyboard(this, builder);
+ }
+ super.dismiss();
+ }
+
+ enum ListType {
+ REGULAR,
+ SINGLE,
+ MULTI;
+
+ public static int getLayoutForType(ListType type) {
+ switch (type) {
+ case REGULAR:
+ return R.layout.md_listitem;
+ case SINGLE:
+ return R.layout.md_listitem_singlechoice;
+ case MULTI:
+ return R.layout.md_listitem_multichoice;
+ default:
+ throw new IllegalArgumentException("Not a valid list type");
+ }
+ }
+ }
+
+ /**
+ * A callback used for regular list dialogs.
+ */
+ public interface ListCallback {
+
+ void onSelection(MaterialDialog dialog, View itemView, int position, CharSequence text);
+ }
+
+ /**
+ * A callback used for regular list dialogs.
+ */
+ public interface ListLongCallback {
+
+ boolean onLongSelection(MaterialDialog dialog, View itemView, int position, CharSequence text);
+ }
+
+ /**
+ * A callback used for multi choice (check box) list dialogs.
+ */
+ public interface ListCallbackSingleChoice {
+
+ /**
+ * Return true to allow the radio button to be checked, if the alwaysCallSingleChoice()
+ * option is used.
+ *
+ * @param dialog The dialog of which a list item was selected.
+ * @param which The index of the item that was selected.
+ * @param text The text of the item that was selected.
+ * @return True to allow the radio button to be selected.
+ */
+ boolean onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text);
+ }
+
+ /**
+ * A callback used for multi choice (check box) list dialogs.
+ */
+ public interface ListCallbackMultiChoice {
+
+ /**
+ * Return true to allow the check box to be checked, if the alwaysCallSingleChoice() option
+ * is used.
+ *
+ * @param dialog The dialog of which a list item was selected.
+ * @param which The indices of the items that were selected.
+ * @param text The text of the items that were selected.
+ * @return True to allow the checkbox to be selected.
+ */
+ boolean onSelection(MaterialDialog dialog, Integer[] which, CharSequence[] text);
+ }
+
+ /**
+ * An alternate way to define a single callback.
+ */
+ public interface SingleButtonCallback {
+
+ void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which);
+ }
+
+ public interface InputCallback {
+
+ void onInput(@NonNull MaterialDialog dialog, CharSequence input);
+ }
+
+ private static class DialogException extends WindowManager.BadTokenException {
+
+ DialogException(@SuppressWarnings("SameParameterValue") String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * The class used to construct a MaterialDialog.
+ */
+ @SuppressWarnings({"WeakerAccess", "unused"})
+ public static class Builder {
+
+ protected final Context context;
+ protected CharSequence title;
+ protected GravityEnum titleGravity = GravityEnum.START;
+ protected GravityEnum contentGravity = GravityEnum.START;
+ protected GravityEnum btnStackedGravity = GravityEnum.END;
+ protected GravityEnum itemsGravity = GravityEnum.START;
+ protected GravityEnum buttonsGravity = GravityEnum.START;
+ protected int buttonRippleColor = 0;
+ protected int titleColor = -1;
+ protected int contentColor = -1;
+ protected CharSequence content;
+ protected ArrayList items;
+ protected CharSequence positiveText;
+ protected CharSequence neutralText;
+ protected CharSequence negativeText;
+ protected boolean positiveFocus;
+ protected boolean neutralFocus;
+ protected boolean negativeFocus;
+ protected View customView;
+ protected int widgetColor;
+ protected ColorStateList choiceWidgetColor;
+ protected ColorStateList positiveColor;
+ protected ColorStateList negativeColor;
+ protected ColorStateList neutralColor;
+ protected ColorStateList linkColor;
+ protected ButtonCallback callback;
+ protected SingleButtonCallback onPositiveCallback;
+ protected SingleButtonCallback onNegativeCallback;
+ protected SingleButtonCallback onNeutralCallback;
+ protected SingleButtonCallback onAnyCallback;
+ protected ListCallback listCallback;
+ protected ListLongCallback listLongCallback;
+ protected ListCallbackSingleChoice listCallbackSingleChoice;
+ protected ListCallbackMultiChoice listCallbackMultiChoice;
+ protected boolean alwaysCallMultiChoiceCallback = false;
+ protected boolean alwaysCallSingleChoiceCallback = false;
+ protected Theme theme = Theme.LIGHT;
+ protected boolean cancelable = true;
+ protected boolean canceledOnTouchOutside = true;
+ protected float contentLineSpacingMultiplier = 1.2f;
+ protected int selectedIndex = -1;
+ protected Integer[] selectedIndices = null;
+ protected Integer[] disabledIndices = null;
+ protected boolean autoDismiss = true;
+ protected Typeface regularFont;
+ protected Typeface mediumFont;
+ protected Drawable icon;
+ protected boolean limitIconToDefaultSize;
+ protected int maxIconSize = -1;
+ protected RecyclerView.Adapter> adapter;
+ protected RecyclerView.LayoutManager layoutManager;
+ protected OnDismissListener dismissListener;
+ protected OnCancelListener cancelListener;
+ protected OnKeyListener keyListener;
+ protected DigitsKeyListener digitsKeyListener;
+ protected OnShowListener showListener;
+ protected StackingBehavior stackingBehavior;
+ protected boolean wrapCustomViewInScroll;
+ protected int dividerColor;
+ protected int backgroundColor;
+ protected int itemColor;
+ protected boolean indeterminateProgress;
+ protected boolean showMinMax;
+ protected int progress = -2;
+ protected int progressMax = 0;
+ protected CharSequence inputPrefill;
+ protected CharSequence inputHint;
+ protected InputCallback inputCallback;
+ protected boolean inputAllowEmpty;
+ protected int inputType = -1;
+ protected boolean alwaysCallInputCallback;
+ protected int inputMinLength = -1;
+ protected int inputMaxLength = -1;
+ protected int inputRangeErrorColor = 0;
+ protected int[] itemIds;
+ protected CharSequence checkBoxPrompt;
+ protected boolean checkBoxPromptInitiallyChecked;
+ protected CheckBox.OnCheckedChangeListener checkBoxPromptListener;
+
+ protected String progressNumberFormat;
+ protected NumberFormat progressPercentFormat;
+ protected boolean indeterminateIsHorizontalProgress;
+
+ protected boolean titleColorSet = false;
+ protected boolean contentColorSet = false;
+ protected boolean itemColorSet = false;
+ protected boolean positiveColorSet = false;
+ protected boolean neutralColorSet = false;
+ protected boolean negativeColorSet = false;
+ protected boolean widgetColorSet = false;
+ protected boolean dividerColorSet = false;
+
+ @DrawableRes
+ protected int listSelector;
+ @DrawableRes
+ protected int btnSelectorStacked;
+ @DrawableRes
+ protected int btnSelectorPositive;
+ @DrawableRes
+ protected int btnSelectorNeutral;
+ @DrawableRes
+ protected int btnSelectorNegative;
+
+ protected Object tag;
+
+ public Builder(@NonNull Context context) {
+ this.context = context;
+ final int materialBlue = DialogUtils.getColor(context, R.color.md_material_blue_600);
+
+ // Retrieve default accent colors, which are used on the action buttons and progress bars
+ this.widgetColor = DialogUtils.resolveColor(context,
+ R.attr.colorAccent, materialBlue);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ this.widgetColor = DialogUtils.resolveColor(context,
+ android.R.attr.colorAccent, this.widgetColor);
+ }
+
+ this.positiveColor = DialogUtils.getActionTextStateList(context, this.widgetColor);
+ this.negativeColor = DialogUtils.getActionTextStateList(context, this.widgetColor);
+ this.neutralColor = DialogUtils.getActionTextStateList(context, this.widgetColor);
+ this.linkColor = DialogUtils.getActionTextStateList(context,
+ DialogUtils.resolveColor(context, R.attr.md_link_color, this.widgetColor));
+
+ int fallback = 0;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ fallback = DialogUtils.resolveColor(context, android.R.attr.colorControlHighlight);
+ }
+ this.buttonRippleColor = DialogUtils.resolveColor(context,
+ R.attr.md_btn_ripple_color,
+ DialogUtils.resolveColor(context,
+ R.attr.colorControlHighlight, fallback));
+
+ this.progressPercentFormat = NumberFormat.getPercentInstance();
+ this.progressNumberFormat = "%1d/%2d";
+
+ // Set the default theme based on the Activity theme's primary color darkness (more white or more black)
+ final int primaryTextColor = DialogUtils.resolveColor(context,
+ android.R.attr.textColorPrimary);
+ this.theme = DialogUtils.isColorDark(primaryTextColor) ? Theme.LIGHT : Theme.DARK;
+
+ // Load theme values from the ThemeSingleton if needed
+ checkSingleton();
+
+ // Retrieve gravity settings from global theme attributes if needed
+ this.titleGravity = DialogUtils.resolveGravityEnum(context,
+ R.attr.md_title_gravity, this.titleGravity);
+ this.contentGravity = DialogUtils.resolveGravityEnum(context,
+ R.attr.md_content_gravity, this.contentGravity);
+ this.btnStackedGravity = DialogUtils.resolveGravityEnum(context,
+ R.attr.md_btnstacked_gravity, this.btnStackedGravity);
+ this.itemsGravity = DialogUtils.resolveGravityEnum(context,
+ R.attr.md_items_gravity, this.itemsGravity);
+ this.buttonsGravity = DialogUtils.resolveGravityEnum(context,
+ R.attr.md_buttons_gravity, this.buttonsGravity);
+
+ final String mediumFont = DialogUtils.resolveString(context,
+ R.attr.md_medium_font);
+ final String regularFont = DialogUtils.resolveString(context,
+ R.attr.md_regular_font);
+ typeface(mediumFont, regularFont);
+
+ if (this.mediumFont == null) {
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ this.mediumFont = Typeface.create("sans-serif-medium", Typeface.NORMAL);
+ } else {
+ this.mediumFont = Typeface.create("sans-serif", Typeface.BOLD);
+ }
+ } catch (Exception ignored) {
+ this.mediumFont = Typeface.DEFAULT_BOLD;
+ }
+ }
+ if (this.regularFont == null) {
+ try {
+ this.regularFont = Typeface.create("sans-serif", Typeface.NORMAL);
+ } catch (Exception ignored) {
+ this.regularFont = Typeface.SANS_SERIF;
+ if (this.regularFont == null) {
+ this.regularFont = Typeface.DEFAULT;
+ }
+ }
+ }
+ }
+
+ public final Context getContext() {
+ return context;
+ }
+
+ public final int getItemColor() {
+ return itemColor;
+ }
+
+ public final Typeface getRegularFont() {
+ return regularFont;
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ private void checkSingleton() {
+ if (ThemeSingleton.get(false) == null) {
+ return;
+ }
+ ThemeSingleton s = ThemeSingleton.get();
+ if (s.darkTheme) {
+ this.theme = Theme.DARK;
+ }
+ if (s.titleColor != 0) {
+ this.titleColor = s.titleColor;
+ }
+ if (s.contentColor != 0) {
+ this.contentColor = s.contentColor;
+ }
+ if (s.positiveColor != null) {
+ this.positiveColor = s.positiveColor;
+ }
+ if (s.neutralColor != null) {
+ this.neutralColor = s.neutralColor;
+ }
+ if (s.negativeColor != null) {
+ this.negativeColor = s.negativeColor;
+ }
+ if (s.itemColor != 0) {
+ this.itemColor = s.itemColor;
+ }
+ if (s.icon != null) {
+ this.icon = s.icon;
+ }
+ if (s.backgroundColor != 0) {
+ this.backgroundColor = s.backgroundColor;
+ }
+ if (s.dividerColor != 0) {
+ this.dividerColor = s.dividerColor;
+ }
+ if (s.btnSelectorStacked != 0) {
+ this.btnSelectorStacked = s.btnSelectorStacked;
+ }
+ if (s.listSelector != 0) {
+ this.listSelector = s.listSelector;
+ }
+ if (s.btnSelectorPositive != 0) {
+ this.btnSelectorPositive = s.btnSelectorPositive;
+ }
+ if (s.btnSelectorNeutral != 0) {
+ this.btnSelectorNeutral = s.btnSelectorNeutral;
+ }
+ if (s.btnSelectorNegative != 0) {
+ this.btnSelectorNegative = s.btnSelectorNegative;
+ }
+ if (s.widgetColor != 0) {
+ this.widgetColor = s.widgetColor;
+ }
+ if (s.linkColor != null) {
+ this.linkColor = s.linkColor;
+ }
+ this.titleGravity = s.titleGravity;
+ this.contentGravity = s.contentGravity;
+ this.btnStackedGravity = s.btnStackedGravity;
+ this.itemsGravity = s.itemsGravity;
+ this.buttonsGravity = s.buttonsGravity;
+ }
+
+ public Builder title(@StringRes int titleRes) {
+ title(this.context.getText(titleRes));
+ return this;
+ }
+
+ public Builder title(@NonNull CharSequence title) {
+ this.title = title;
+ return this;
+ }
+
+ public Builder titleGravity(@NonNull GravityEnum gravity) {
+ this.titleGravity = gravity;
+ return this;
+ }
+
+ public Builder buttonRippleColor(@ColorInt int color) {
+ this.buttonRippleColor = color;
+ return this;
+ }
+
+ public Builder buttonRippleColorRes(@ColorRes int colorRes) {
+ return buttonRippleColor(DialogUtils.getColor(this.context, colorRes));
+ }
+
+ public Builder buttonRippleColorAttr(@AttrRes int colorAttr) {
+ return buttonRippleColor(DialogUtils.resolveColor(this.context, colorAttr));
+ }
+
+ public Builder titleColor(@ColorInt int color) {
+ this.titleColor = color;
+ this.titleColorSet = true;
+ return this;
+ }
+
+ public Builder titleColorRes(@ColorRes int colorRes) {
+ return titleColor(DialogUtils.getColor(this.context, colorRes));
+ }
+
+ public Builder titleColorAttr(@AttrRes int colorAttr) {
+ return titleColor(DialogUtils.resolveColor(this.context, colorAttr));
+ }
+
+ /**
+ * Sets the fonts used in the dialog. It's recommended that you use {@link #typeface(String,
+ * String)} instead, to avoid duplicate Typeface allocations and high memory usage.
+ *
+ * @param medium The font used on titles and action buttons. Null uses device default.
+ * @param regular The font used everywhere else, like on the content and list items. Null uses
+ * device default.
+ * @return The Builder instance so you can chain calls to it.
+ */
+ public Builder typeface(@Nullable Typeface medium, @Nullable Typeface regular) {
+ this.mediumFont = medium;
+ this.regularFont = regular;
+ return this;
+ }
+
+ /**
+ * Sets the fonts used in the dialog, by file names. This also uses TypefaceHelper in order
+ * to avoid any un-needed allocations (it recycles typefaces for you).
+ *
+ * @param medium The name of font in assets/fonts used on titles and action buttons (null uses
+ * device default). E.g. [your-project]/app/main/assets/fonts/[medium]
+ * @param regular The name of font in assets/fonts used everywhere else, like content and list
+ * items (null uses device default). E.g. [your-project]/app/main/assets/fonts/[regular]
+ * @return The Builder instance so you can chain calls to it.
+ */
+ public Builder typeface(@Nullable String medium, @Nullable String regular) {
+ if (medium != null) {
+ this.mediumFont = TypefaceHelper.get(this.context, medium);
+ if (this.mediumFont == null) {
+ throw new IllegalArgumentException("No font asset found for " + medium);
+ }
+ }
+ if (regular != null) {
+ this.regularFont = TypefaceHelper.get(this.context, regular);
+ if (this.regularFont == null) {
+ throw new IllegalArgumentException("No font asset found for " + regular);
+ }
+ }
+ return this;
+ }
+
+ public Builder icon(@NonNull Drawable icon) {
+ this.icon = icon;
+ return this;
+ }
+
+ public Builder iconRes(@DrawableRes int icon) {
+ this.icon = ResourcesCompat.getDrawable(context.getResources(), icon, null);
+ return this;
+ }
+
+ public Builder iconAttr(@AttrRes int iconAttr) {
+ this.icon = DialogUtils.resolveDrawable(context, iconAttr);
+ return this;
+ }
+
+ public Builder content(@StringRes int contentRes) {
+ return content(contentRes, false);
+ }
+
+ public Builder content(@StringRes int contentRes, boolean html) {
+ CharSequence text = this.context.getText(contentRes);
+ if (html) {
+ text = Html.fromHtml(text.toString().replace("\n", " "));
+ }
+ return content(text);
+ }
+
+ public Builder content(@NonNull CharSequence content) {
+ if (this.customView != null) {
+ throw new IllegalStateException("You cannot set content() " +
+ "when you're using a custom view.");
+ }
+ this.content = content;
+ return this;
+ }
+
+ public Builder content(@StringRes int contentRes, Object... formatArgs) {
+ String str = String.format(this.context.getString(contentRes), formatArgs)
+ .replace("\n", " ");
+ //noinspection deprecation
+ return content(Html.fromHtml(str));
+ }
+
+ public Builder contentColor(@ColorInt int color) {
+ this.contentColor = color;
+ this.contentColorSet = true;
+ return this;
+ }
+
+ public Builder contentColorRes(@ColorRes int colorRes) {
+ contentColor(DialogUtils.getColor(this.context, colorRes));
+ return this;
+ }
+
+ public Builder contentColorAttr(@AttrRes int colorAttr) {
+ contentColor(DialogUtils.resolveColor(this.context, colorAttr));
+ return this;
+ }
+
+ public Builder contentGravity(@NonNull GravityEnum gravity) {
+ this.contentGravity = gravity;
+ return this;
+ }
+
+ public Builder contentLineSpacing(float multiplier) {
+ this.contentLineSpacingMultiplier = multiplier;
+ return this;
+ }
+
+ public Builder items(@NonNull Collection collection) {
+ if (collection.size() > 0) {
+ final CharSequence[] array = new CharSequence[collection.size()];
+ int i = 0;
+ for (Object obj : collection) {
+ array[i] = obj.toString();
+ i++;
+ }
+ items(array);
+ } else if (collection.size() == 0) {
+ items = new ArrayList<>();
+ }
+ return this;
+ }
+
+ public Builder items(@ArrayRes int itemsRes) {
+ items(this.context.getResources().getTextArray(itemsRes));
+ return this;
+ }
+
+ public Builder items(@NonNull CharSequence... items) {
+ if (this.customView != null) {
+ throw new IllegalStateException("You cannot set items()" +
+ " when you're using a custom view.");
+ }
+ this.items = new ArrayList<>();
+ Collections.addAll(this.items, items);
+ return this;
+ }
+
+ public Builder itemsCallback(@NonNull ListCallback callback) {
+ this.listCallback = callback;
+ this.listCallbackSingleChoice = null;
+ this.listCallbackMultiChoice = null;
+ return this;
+ }
+
+ public Builder itemsLongCallback(@NonNull ListLongCallback callback) {
+ this.listLongCallback = callback;
+ this.listCallbackSingleChoice = null;
+ this.listCallbackMultiChoice = null;
+ return this;
+ }
+
+ public Builder itemsColor(@ColorInt int color) {
+ this.itemColor = color;
+ this.itemColorSet = true;
+ return this;
+ }
+
+ public Builder itemsColorRes(@ColorRes int colorRes) {
+ return itemsColor(DialogUtils.getColor(this.context, colorRes));
+ }
+
+ public Builder itemsColorAttr(@AttrRes int colorAttr) {
+ return itemsColor(DialogUtils.resolveColor(this.context, colorAttr));
+ }
+
+ public Builder itemsGravity(@NonNull GravityEnum gravity) {
+ this.itemsGravity = gravity;
+ return this;
+ }
+
+ public Builder itemsIds(@NonNull int[] idsArray) {
+ this.itemIds = idsArray;
+ return this;
+ }
+
+ public Builder itemsIds(@ArrayRes int idsArrayRes) {
+ return itemsIds(context.getResources().getIntArray(idsArrayRes));
+ }
+
+ public Builder buttonsGravity(@NonNull GravityEnum gravity) {
+ this.buttonsGravity = gravity;
+ return this;
+ }
+
+ /**
+ * Pass anything below 0 (such as -1) for the selected index to leave all options unselected
+ * initially. Otherwise pass the index of an item that will be selected initially.
+ *
+ * @param selectedIndex The checkbox index that will be selected initially.
+ * @param callback The callback that will be called when the presses the positive button.
+ * @return The Builder instance so you can chain calls to it.
+ */
+ public Builder itemsCallbackSingleChoice(int selectedIndex,
+ @NonNull ListCallbackSingleChoice callback) {
+ this.selectedIndex = selectedIndex;
+ this.listCallback = null;
+ this.listCallbackSingleChoice = callback;
+ this.listCallbackMultiChoice = null;
+ return this;
+ }
+
+ /**
+ * By default, the single choice callback is only called when the user clicks the positive
+ * button or if there are no buttons. Call this to force it to always call on item clicks
+ * even if the positive button exists.
+ *
+ * @return The Builder instance so you can chain calls to it.
+ */
+ public Builder alwaysCallSingleChoiceCallback() {
+ this.alwaysCallSingleChoiceCallback = true;
+ return this;
+ }
+
+ /**
+ * Pass null for the selected indices to leave all options unselected initially. Otherwise
+ * pass an array of indices that will be selected initially.
+ *
+ * @param selectedIndices The radio button indices that will be selected initially.
+ * @param callback The callback that will be called when the presses the positive button.
+ * @return The Builder instance so you can chain calls to it.
+ */
+ public Builder itemsCallbackMultiChoice(@Nullable Integer[] selectedIndices,
+ @NonNull ListCallbackMultiChoice callback) {
+ this.selectedIndices = selectedIndices;
+ this.listCallback = null;
+ this.listCallbackSingleChoice = null;
+ this.listCallbackMultiChoice = callback;
+ return this;
+ }
+
+ /**
+ * Sets indices of items that are not clickable. If they are checkboxes or radio buttons,
+ * they will not be toggleable.
+ *
+ * @param disabledIndices The item indices that will be disabled from selection.
+ * @return The Builder instance so you can chain calls to it.
+ */
+ public Builder itemsDisabledIndices(@Nullable Integer... disabledIndices) {
+ this.disabledIndices = disabledIndices;
+ return this;
+ }
+
+ /**
+ * By default, the multi choice callback is only called when the user clicks the positive
+ * button or if there are no buttons. Call this to force it to always call on item clicks
+ * even if the positive button exists.
+ *
+ * @return The Builder instance so you can chain calls to it.
+ */
+ public Builder alwaysCallMultiChoiceCallback() {
+ this.alwaysCallMultiChoiceCallback = true;
+ return this;
+ }
+
+ public Builder positiveText(@StringRes int positiveRes) {
+ if (positiveRes == 0) {
+ return this;
+ }
+ positiveText(this.context.getText(positiveRes));
+ return this;
+ }
+
+ public Builder positiveText(@NonNull CharSequence message) {
+ this.positiveText = message;
+ return this;
+ }
+
+ public Builder positiveColor(@ColorInt int color) {
+ return positiveColor(DialogUtils.getActionTextStateList(context, color));
+ }
+
+ public Builder positiveColorRes(@ColorRes int colorRes) {
+ return positiveColor(DialogUtils.getActionTextColorStateList(this.context, colorRes));
+ }
+
+ public Builder positiveColorAttr(@AttrRes int colorAttr) {
+ return positiveColor(
+ DialogUtils.resolveActionTextColorStateList(this.context, colorAttr, null));
+ }
+
+ public Builder positiveColor(@NonNull ColorStateList colorStateList) {
+ this.positiveColor = colorStateList;
+ this.positiveColorSet = true;
+ return this;
+ }
+
+ public Builder positiveFocus(boolean isFocusedDefault) {
+ this.positiveFocus = isFocusedDefault;
+ return this;
+ }
+
+ public Builder neutralText(@StringRes int neutralRes) {
+ if (neutralRes == 0) {
+ return this;
+ }
+ return neutralText(this.context.getText(neutralRes));
+ }
+
+ public Builder neutralText(@NonNull CharSequence message) {
+ this.neutralText = message;
+ return this;
+ }
+
+ public Builder negativeColor(@ColorInt int color) {
+ return negativeColor(DialogUtils.getActionTextStateList(context, color));
+ }
+
+ public Builder negativeColorRes(@ColorRes int colorRes) {
+ return negativeColor(DialogUtils.getActionTextColorStateList(this.context, colorRes));
+ }
+
+ public Builder negativeColorAttr(@AttrRes int colorAttr) {
+ return negativeColor(
+ DialogUtils.resolveActionTextColorStateList(this.context, colorAttr, null));
+ }
+
+ public Builder negativeColor(@NonNull ColorStateList colorStateList) {
+ this.negativeColor = colorStateList;
+ this.negativeColorSet = true;
+ return this;
+ }
+
+ public Builder negativeText(@StringRes int negativeRes) {
+ if (negativeRes == 0) {
+ return this;
+ }
+ return negativeText(this.context.getText(negativeRes));
+ }
+
+ public Builder negativeText(@NonNull CharSequence message) {
+ this.negativeText = message;
+ return this;
+ }
+
+ public Builder negativeFocus(boolean isFocusedDefault) {
+ this.negativeFocus = isFocusedDefault;
+ return this;
+ }
+
+ public Builder neutralColor(@ColorInt int color) {
+ return neutralColor(DialogUtils.getActionTextStateList(context, color));
+ }
+
+ public Builder neutralColorRes(@ColorRes int colorRes) {
+ return neutralColor(DialogUtils.getActionTextColorStateList(this.context, colorRes));
+ }
+
+ public Builder neutralColorAttr(@AttrRes int colorAttr) {
+ return neutralColor(
+ DialogUtils.resolveActionTextColorStateList(this.context, colorAttr, null));
+ }
+
+ public Builder neutralColor(@NonNull ColorStateList colorStateList) {
+ this.neutralColor = colorStateList;
+ this.neutralColorSet = true;
+ return this;
+ }
+
+ public Builder neutralFocus(boolean isFocusedDefault) {
+ this.neutralFocus = isFocusedDefault;
+ return this;
+ }
+
+ public Builder linkColor(@ColorInt int color) {
+ return linkColor(DialogUtils.getActionTextStateList(context, color));
+ }
+
+ public Builder linkColorRes(@ColorRes int colorRes) {
+ return linkColor(DialogUtils.getActionTextColorStateList(this.context, colorRes));
+ }
+
+ public Builder linkColorAttr(@AttrRes int colorAttr) {
+ return linkColor(DialogUtils.resolveActionTextColorStateList(this.context, colorAttr, null));
+ }
+
+ public Builder linkColor(@NonNull ColorStateList colorStateList) {
+ this.linkColor = colorStateList;
+ return this;
+ }
+
+ public Builder listSelector(@DrawableRes int selectorRes) {
+ this.listSelector = selectorRes;
+ return this;
+ }
+
+ public Builder btnSelectorStacked(@DrawableRes int selectorRes) {
+ this.btnSelectorStacked = selectorRes;
+ return this;
+ }
+
+ public Builder btnSelector(@DrawableRes int selectorRes) {
+ this.btnSelectorPositive = selectorRes;
+ this.btnSelectorNeutral = selectorRes;
+ this.btnSelectorNegative = selectorRes;
+ return this;
+ }
+
+ public Builder btnSelector(@DrawableRes int selectorRes, @NonNull DialogAction which) {
+ switch (which) {
+ default:
+ this.btnSelectorPositive = selectorRes;
+ break;
+ case NEUTRAL:
+ this.btnSelectorNeutral = selectorRes;
+ break;
+ case NEGATIVE:
+ this.btnSelectorNegative = selectorRes;
+ break;
+ }
+ return this;
+ }
+
+ /**
+ * Sets the gravity used for the text in stacked action buttons. By default, it's #{@link
+ * GravityEnum#END}.
+ *
+ * @param gravity The gravity to use.
+ * @return The Builder instance so calls can be chained.
+ */
+ public Builder btnStackedGravity(@NonNull GravityEnum gravity) {
+ this.btnStackedGravity = gravity;
+ return this;
+ }
+
+ public Builder checkBoxPrompt(@NonNull CharSequence prompt, boolean initiallyChecked,
+ @Nullable CheckBox.OnCheckedChangeListener checkListener) {
+ this.checkBoxPrompt = prompt;
+ this.checkBoxPromptInitiallyChecked = initiallyChecked;
+ this.checkBoxPromptListener = checkListener;
+ return this;
+ }
+
+ public Builder checkBoxPromptRes(@StringRes int prompt, boolean initiallyChecked,
+ @Nullable CheckBox.OnCheckedChangeListener checkListener) {
+ return checkBoxPrompt(context.getResources().getText(prompt),
+ initiallyChecked, checkListener);
+ }
+
+ public Builder customView(@LayoutRes int layoutRes, boolean wrapInScrollView) {
+ LayoutInflater li = LayoutInflater.from(this.context);
+ return customView(li.inflate(layoutRes, null), wrapInScrollView);
+ }
+
+ public Builder customView(@NonNull View view, boolean wrapInScrollView) {
+ if (this.content != null) {
+ throw new IllegalStateException("You cannot use customView() when you have content set.");
+ } else if (this.items != null) {
+ throw new IllegalStateException("You cannot use customView() when you have items set.");
+ } else if (this.inputCallback != null) {
+ throw new IllegalStateException("You cannot use customView() with an input dialog");
+ } else if (this.progress > -2 || this.indeterminateProgress) {
+ throw new IllegalStateException("You cannot use customView() with a progress dialog");
+ }
+ if (view.getParent() != null && view.getParent() instanceof ViewGroup) {
+ ((ViewGroup) view.getParent()).removeView(view);
+ }
+ this.customView = view;
+ this.wrapCustomViewInScroll = wrapInScrollView;
+ return this;
+ }
+
+ /**
+ * Makes this dialog a progress dialog.
+ *
+ * @param indeterminate If true, an infinite circular spinner is shown. If false, a horizontal
+ * progress bar is shown that is incremented or set via the built MaterialDialog instance.
+ * @param max When indeterminate is false, the max value the horizontal progress bar can get
+ * to.
+ * @return An instance of the Builder so calls can be chained.
+ */
+ public Builder progress(boolean indeterminate, int max) {
+ if (this.customView != null) {
+ throw new IllegalStateException(
+ "You cannot set progress() when you're using a custom view.");
+ }
+ if (indeterminate) {
+ this.indeterminateProgress = true;
+ this.progress = -2;
+ } else {
+ this.indeterminateIsHorizontalProgress = false;
+ this.indeterminateProgress = false;
+ this.progress = -1;
+ this.progressMax = max;
+ }
+ return this;
+ }
+
+ /**
+ * Makes this dialog a progress dialog.
+ *
+ * @param indeterminate If true, an infinite circular spinner is shown. If false, a horizontal
+ * progress bar is shown that is incremented or set via the built MaterialDialog instance.
+ * @param max When indeterminate is false, the max value the horizontal progress bar can get
+ * to.
+ * @param showMinMax For determinate dialogs, the min and max will be displayed to the left
+ * (start) of the progress bar, e.g. 50/100.
+ * @return An instance of the Builder so calls can be chained.
+ */
+ public Builder progress(boolean indeterminate, int max, boolean showMinMax) {
+ this.showMinMax = showMinMax;
+ return progress(indeterminate, max);
+ }
+
+ /**
+ * hange the format of the small text showing current and maximum units of progress. The
+ * default is "%1d/%2d".
+ */
+ public Builder progressNumberFormat(@NonNull String format) {
+ this.progressNumberFormat = format;
+ return this;
+ }
+
+ /**
+ * Change the format of the small text showing the percentage of progress. The default is
+ * NumberFormat.getPercentageInstance().
+ */
+ public Builder progressPercentFormat(@NonNull NumberFormat format) {
+ this.progressPercentFormat = format;
+ return this;
+ }
+
+ /**
+ * By default, indeterminate progress dialogs will use a circular indicator. You can change
+ * it to use a horizontal progress indicator.
+ */
+ public Builder progressIndeterminateStyle(boolean horizontal) {
+ this.indeterminateIsHorizontalProgress = horizontal;
+ return this;
+ }
+
+ public Builder widgetColor(@ColorInt int color) {
+ this.widgetColor = color;
+ this.widgetColorSet = true;
+ return this;
+ }
+
+ public Builder widgetColorRes(@ColorRes int colorRes) {
+ return widgetColor(DialogUtils.getColor(this.context, colorRes));
+ }
+
+ public Builder widgetColorAttr(@AttrRes int colorAttr) {
+ return widgetColor(DialogUtils.resolveColor(this.context, colorAttr));
+ }
+
+ public Builder choiceWidgetColor(@Nullable ColorStateList colorStateList) {
+ this.choiceWidgetColor = colorStateList;
+ return this;
+ }
+
+ public Builder dividerColor(@ColorInt int color) {
+ this.dividerColor = color;
+ this.dividerColorSet = true;
+ return this;
+ }
+
+ public Builder dividerColorRes(@ColorRes int colorRes) {
+ return dividerColor(DialogUtils.getColor(this.context, colorRes));
+ }
+
+ public Builder dividerColorAttr(@AttrRes int colorAttr) {
+ return dividerColor(DialogUtils.resolveColor(this.context, colorAttr));
+ }
+
+ public Builder backgroundColor(@ColorInt int color) {
+ this.backgroundColor = color;
+ return this;
+ }
+
+ public Builder backgroundColorRes(@ColorRes int colorRes) {
+ return backgroundColor(DialogUtils.getColor(this.context, colorRes));
+ }
+
+ public Builder backgroundColorAttr(@AttrRes int colorAttr) {
+ return backgroundColor(DialogUtils.resolveColor(this.context, colorAttr));
+ }
+
+ public Builder callback(@NonNull ButtonCallback callback) {
+ this.callback = callback;
+ return this;
+ }
+
+ public Builder onPositive(@NonNull SingleButtonCallback callback) {
+ this.onPositiveCallback = callback;
+ return this;
+ }
+
+ public Builder onNegative(@NonNull SingleButtonCallback callback) {
+ this.onNegativeCallback = callback;
+ return this;
+ }
+
+ public Builder onNeutral(@NonNull SingleButtonCallback callback) {
+ this.onNeutralCallback = callback;
+ return this;
+ }
+
+ public Builder onAny(@NonNull SingleButtonCallback callback) {
+ this.onAnyCallback = callback;
+ return this;
+ }
+
+ public Builder theme(@NonNull Theme theme) {
+ this.theme = theme;
+ return this;
+ }
+
+ public Builder cancelable(boolean cancelable) {
+ this.cancelable = cancelable;
+ this.canceledOnTouchOutside = cancelable;
+ return this;
+ }
+
+ public Builder canceledOnTouchOutside(boolean canceledOnTouchOutside) {
+ this.canceledOnTouchOutside = canceledOnTouchOutside;
+ return this;
+ }
+
+ /**
+ * This defaults to true. If set to false, the dialog will not automatically be dismissed
+ * when an action button is pressed, and not automatically dismissed when the user selects a
+ * list item.
+ *
+ * @param dismiss Whether or not to dismiss the dialog automatically.
+ * @return The Builder instance so you can chain calls to it.
+ */
+ public Builder autoDismiss(boolean dismiss) {
+ this.autoDismiss = dismiss;
+ return this;
+ }
+
+ /**
+ * Sets a custom {@link RecyclerView.Adapter} for the dialog's
+ * list
+ *
+ * @param adapter The adapter to set to the list.
+ * @param layoutManager The layout manager to use in the RecyclerView. Pass null to use the
+ * default linear manager.
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ @SuppressWarnings("ConstantConditions")
+ public Builder adapter(
+ @NonNull RecyclerView.Adapter> adapter,
+ @Nullable RecyclerView.LayoutManager layoutManager) {
+ if (this.customView != null) {
+ throw new IllegalStateException("You cannot set adapter() when " +
+ "you're using a custom view.");
+ }
+ if (layoutManager != null && !(layoutManager instanceof LinearLayoutManager) &&
+ !(layoutManager instanceof GridLayoutManager)) {
+ throw new IllegalStateException("You can currently only use LinearLayoutManager" +
+ " and GridLayoutManager with this library.");
+ }
+ this.adapter = adapter;
+ this.layoutManager = layoutManager;
+ return this;
+ }
+
+ /**
+ * Limits the display size of a set icon to 48dp.
+ */
+ public Builder limitIconToDefaultSize() {
+ this.limitIconToDefaultSize = true;
+ return this;
+ }
+
+ public Builder maxIconSize(int maxIconSize) {
+ this.maxIconSize = maxIconSize;
+ return this;
+ }
+
+ public Builder maxIconSizeRes(@DimenRes int maxIconSizeRes) {
+ return maxIconSize((int) this.context.getResources().getDimension(maxIconSizeRes));
+ }
+
+ public Builder showListener(@NonNull OnShowListener listener) {
+ this.showListener = listener;
+ return this;
+ }
+
+ public Builder dismissListener(@NonNull OnDismissListener listener) {
+ this.dismissListener = listener;
+ return this;
+ }
+
+ public Builder cancelListener(@NonNull OnCancelListener listener) {
+ this.cancelListener = listener;
+ return this;
+ }
+
+ public Builder keyListener(@NonNull OnKeyListener listener) {
+ this.keyListener = listener;
+ return this;
+ }
+
+ public Builder DigitsKeyListener(@NonNull DigitsKeyListener digitsKeyListener) {
+ this.digitsKeyListener = digitsKeyListener;
+ return this;
+ }
+
+ /**
+ * Sets action button stacking behavior.
+ *
+ * @param behavior The behavior of the action button stacking logic.
+ * @return The Builder instance so you can chain calls to it.
+ */
+ public Builder stackingBehavior(@NonNull StackingBehavior behavior) {
+ this.stackingBehavior = behavior;
+ return this;
+ }
+
+ public Builder input(@Nullable CharSequence hint, @Nullable CharSequence prefill,
+ boolean allowEmptyInput, @NonNull InputCallback callback) {
+ if (this.customView != null) {
+ throw new IllegalStateException("You cannot set content() when " +
+ "you're using a custom view.");
+ }
+ this.inputCallback = callback;
+ this.inputHint = hint;
+ this.inputPrefill = prefill;
+ this.inputAllowEmpty = allowEmptyInput;
+ return this;
+ }
+
+ public Builder input(@Nullable CharSequence hint, @Nullable CharSequence prefill,
+ @NonNull InputCallback callback) {
+ return input(hint, prefill, true, callback);
+ }
+
+ public Builder input(@StringRes int hint, @StringRes int prefill,
+ boolean allowEmptyInput, @NonNull InputCallback callback) {
+ return input(hint == 0 ? null : context.getText(hint), prefill == 0 ? null :
+ context.getText(prefill), allowEmptyInput, callback);
+ }
+
+ public Builder input(@StringRes int hint, @StringRes int prefill,
+ @NonNull InputCallback callback) {
+ return input(hint, prefill, true, callback);
+ }
+
+ public Builder inputType(int type) {
+ this.inputType = type;
+ return this;
+ }
+
+ public Builder inputRange(@IntRange(from = 0, to = Integer.MAX_VALUE) int minLength,
+ @IntRange(from = -1, to = Integer.MAX_VALUE) int maxLength) {
+ return inputRange(minLength, maxLength, 0);
+ }
+
+ /**
+ * @param errorColor Pass in 0 for the default red error color (as specified in guidelines).
+ */
+ public Builder inputRange(@IntRange(from = 0, to = Integer.MAX_VALUE) int minLength,
+ @IntRange(from = -1, to = Integer.MAX_VALUE) int maxLength,
+ @ColorInt int errorColor) {
+ if (minLength < 0) {
+ throw new IllegalArgumentException("Min length for input dialogs " +
+ "cannot be less than 0.");
+ }
+ this.inputMinLength = minLength;
+ this.inputMaxLength = maxLength;
+ if (errorColor == 0) {
+ this.inputRangeErrorColor = DialogUtils.getColor(context,
+ R.color.md_edittext_error);
+ } else {
+ this.inputRangeErrorColor = errorColor;
+ }
+ if (this.inputMinLength > 0) {
+ this.inputAllowEmpty = false;
+ }
+ return this;
+ }
+
+ /**
+ * Same as #{@link #inputRange(int, int, int)}, but it takes a color resource ID for the
+ * error color.
+ */
+ public Builder inputRangeRes(@IntRange(from = 0, to = Integer.MAX_VALUE) int minLength,
+ @IntRange(from = -1, to = Integer.MAX_VALUE) int maxLength,
+ @ColorRes int errorColor) {
+ return inputRange(minLength, maxLength,
+ DialogUtils.getColor(context, errorColor));
+ }
+
+ public Builder alwaysCallInputCallback() {
+ this.alwaysCallInputCallback = true;
+ return this;
+ }
+
+ public Builder tag(@Nullable Object tag) {
+ this.tag = tag;
+ return this;
+ }
+
+ @UiThread
+ public MaterialDialog build() {
+ return new MaterialDialog(this);
+ }
+
+ @UiThread
+ public MaterialDialog show() {
+ MaterialDialog dialog = build();
+ dialog.show();
+ return dialog;
+ }
+ }
+
+ /**
+ * Override these as needed, so no needing to sub empty methods from an interface
+ *
+ * @deprecated Use the individual onPositive, onNegative, onNeutral, or onAny Builder methods
+ * instead.
+ */
+ @SuppressWarnings({"WeakerAccess", "UnusedParameters"})
+ @Deprecated
+ public static abstract class ButtonCallback {
+
+ public ButtonCallback() {
+ super();
+ }
+
+ @Deprecated
+ public void onAny(MaterialDialog dialog) {
+ }
+
+ @Deprecated
+ public void onPositive(MaterialDialog dialog) {
+ }
+
+ @Deprecated
+ public void onNegative(MaterialDialog dialog) {
+ }
+
+ // The overidden methods below prevent Android Studio from suggesting that they are overidden by developers
+
+ @Deprecated
+ public void onNeutral(MaterialDialog dialog) {
+ }
+
+ @Override
+ protected final Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ return super.equals(o);
+ }
+
+ @Override
+ protected final void finalize() throws Throwable {
+ super.finalize();
+ }
+
+ @Override
+ public final int hashCode() {
+ return super.hashCode();
+ }
+
+ @Override
+ public final String toString() {
+ return super.toString();
+ }
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/StackingBehavior.java b/widget/src/main/java/com/hzecool/widget/materialdialog/StackingBehavior.java
new file mode 100644
index 0000000..112a86a
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/StackingBehavior.java
@@ -0,0 +1,19 @@
+package com.hzecool.widget.materialdialog;
+
+/**
+ * @author Aidan Follestad (afollestad)
+ */
+public enum StackingBehavior {
+ /**
+ * The action buttons are always stacked vertically.
+ */
+ ALWAYS,
+ /**
+ * The action buttons are stacked vertically IF it is necessary for them to fit in the dialog.
+ */
+ ADAPTIVE,
+ /**
+ * The action buttons are never stacked, even if they should be.
+ */
+ NEVER
+}
\ No newline at end of file
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/Theme.java b/widget/src/main/java/com/hzecool/widget/materialdialog/Theme.java
new file mode 100644
index 0000000..5f2cf77
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/Theme.java
@@ -0,0 +1,8 @@
+package com.hzecool.widget.materialdialog;
+
+/**
+ * @author Aidan Follestad (afollestad)
+ */
+public enum Theme {
+ LIGHT, DARK
+}
\ No newline at end of file
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/internal/MDAdapter.java b/widget/src/main/java/com/hzecool/widget/materialdialog/internal/MDAdapter.java
new file mode 100644
index 0000000..7ca27d1
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/internal/MDAdapter.java
@@ -0,0 +1,12 @@
+package com.hzecool.widget.materialdialog.internal;
+
+
+import com.hzecool.widget.materialdialog.MaterialDialog;
+
+/**
+ * @author Aidan Follestad (afollestad)
+ */
+public interface MDAdapter {
+
+ void setDialog(MaterialDialog dialog);
+}
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/internal/MDButton.java b/widget/src/main/java/com/hzecool/widget/materialdialog/internal/MDButton.java
new file mode 100644
index 0000000..5ba7d82
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/internal/MDButton.java
@@ -0,0 +1,97 @@
+package com.hzecool.widget.materialdialog.internal;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v7.text.AllCapsTransformationMethod;
+import android.support.v7.widget.AppCompatTextView;
+import android.util.AttributeSet;
+import android.view.Gravity;
+
+import com.hzecool.widget.R;
+import com.hzecool.widget.materialdialog.GravityEnum;
+import com.hzecool.widget.materialdialog.util.DialogUtils;
+
+
+/**
+ * @author Kevin Barry (teslacoil) 4/02/2015
+ */
+public class MDButton extends AppCompatTextView {
+
+ private boolean stacked = false;
+ private GravityEnum stackedGravity;
+
+ private int stackedEndPadding;
+ private Drawable stackedBackground;
+ private Drawable defaultBackground;
+
+ public MDButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public MDButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context);
+ }
+
+ private void init(Context context) {
+ stackedEndPadding = context.getResources()
+ .getDimensionPixelSize(R.dimen.md_dialog_frame_margin);
+ stackedGravity = GravityEnum.END;
+ }
+
+ /**
+ * Set if the button should be displayed in stacked mode.
+ * This should only be called from MDRootLayout's onMeasure, and we must be measured
+ * after calling this.
+ */
+ /* package */ void setStacked(boolean stacked, boolean force) {
+ if (this.stacked != stacked || force) {
+
+ setGravity(
+ stacked ? (Gravity.CENTER_VERTICAL | stackedGravity.getGravityInt()) : Gravity.CENTER);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ //noinspection ResourceType
+ setTextAlignment(stacked ? stackedGravity.getTextAlignment() : TEXT_ALIGNMENT_CENTER);
+ }
+
+ DialogUtils.setBackgroundCompat(this, stacked ? stackedBackground : defaultBackground);
+ if (stacked) {
+ setPadding(stackedEndPadding, getPaddingTop(), stackedEndPadding, getPaddingBottom());
+ } /* Else the padding was properly reset by the drawable */
+
+ this.stacked = stacked;
+ }
+ }
+
+ public void setStackedGravity(GravityEnum gravity) {
+ stackedGravity = gravity;
+ }
+
+ public void setStackedSelector(Drawable d) {
+ stackedBackground = d;
+ if (stacked) {
+ setStacked(true, true);
+ }
+ }
+
+ public void setDefaultSelector(Drawable d) {
+ defaultBackground = d;
+ if (!stacked) {
+ setStacked(false, true);
+ }
+ }
+
+ public void setAllCapsCompat(boolean allCaps) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ setAllCaps(allCaps);
+ } else {
+ if (allCaps) {
+ setTransformationMethod(new AllCapsTransformationMethod(getContext()));
+ } else {
+ setTransformationMethod(null);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/internal/MDRootLayout.java b/widget/src/main/java/com/hzecool/widget/materialdialog/internal/MDRootLayout.java
new file mode 100644
index 0000000..2f3a128
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/internal/MDRootLayout.java
@@ -0,0 +1,631 @@
+package com.hzecool.widget.materialdialog.internal;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Build;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.webkit.WebView;
+import android.widget.AdapterView;
+import android.widget.ScrollView;
+
+import com.hzecool.widget.R;
+import com.hzecool.widget.materialdialog.GravityEnum;
+import com.hzecool.widget.materialdialog.StackingBehavior;
+import com.hzecool.widget.materialdialog.util.DialogUtils;
+
+/**
+ * @author Kevin Barry (teslacoil) 4/02/2015 This is the top level view for all MaterialDialogs It
+ * handles the layout of: titleFrame (md_stub_titleframe) content (text, custom view,
+ * listview, etc) buttonDefault... (either stacked or horizontal)
+ */
+public class MDRootLayout extends ViewGroup {
+
+ private static final int INDEX_NEUTRAL = 0;
+ private static final int INDEX_NEGATIVE = 1;
+ private static final int INDEX_POSITIVE = 2;
+ private final MDButton[] buttons = new MDButton[3];
+ private int maxHeight;
+ private View titleBar;
+ private View content;
+ private boolean drawTopDivider = false;
+ private boolean drawBottomDivider = false;
+ private StackingBehavior stackBehavior = StackingBehavior.ADAPTIVE;
+ private boolean isStacked = false;
+ private boolean useFullPadding = true;
+ private boolean reducePaddingNoTitleNoButtons;
+ private boolean noTitleNoPadding;
+
+ private int noTitlePaddingFull;
+ private int buttonPaddingFull;
+ private int buttonBarHeight;
+
+ private GravityEnum buttonGravity = GravityEnum.START;
+
+ /* Margin from dialog frame to first button */
+ private int buttonHorizontalEdgeMargin;
+
+ private Paint dividerPaint;
+
+ private ViewTreeObserver.OnScrollChangedListener topOnScrollChangedListener;
+ private ViewTreeObserver.OnScrollChangedListener bottomOnScrollChangedListener;
+ private int dividerWidth;
+
+ public MDRootLayout(Context context) {
+ super(context);
+ init(context, null, 0);
+ }
+
+ public MDRootLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs, 0);
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public MDRootLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs, defStyleAttr);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public MDRootLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init(context, attrs, defStyleAttr);
+ }
+
+ private static boolean isVisible(View v) {
+ boolean visible = v != null && v.getVisibility() != View.GONE;
+ if (visible && v instanceof MDButton) {
+ visible = ((MDButton) v).getText().toString().trim().length() > 0;
+ }
+ return visible;
+ }
+
+ public static boolean canRecyclerViewScroll(RecyclerView view) {
+ return view != null && view.getLayoutManager() != null && view.getLayoutManager()
+ .canScrollVertically();
+ }
+
+ private static boolean canScrollViewScroll(ScrollView sv) {
+ if (sv.getChildCount() == 0) {
+ return false;
+ }
+ final int childHeight = sv.getChildAt(0).getMeasuredHeight();
+ return sv.getMeasuredHeight() - sv.getPaddingTop() - sv.getPaddingBottom() < childHeight;
+ }
+
+ private static boolean canWebViewScroll(WebView view) {
+ //noinspection deprecation
+ return view.getMeasuredHeight() < view.getContentHeight() * view.getScale();
+ }
+
+ private static boolean canAdapterViewScroll(AdapterView lv) {
+ /* Force it to layout it's children */
+ if (lv.getLastVisiblePosition() == -1) {
+ return false;
+ }
+
+ /* We can scroll if the first or last item is not visible */
+ boolean firstItemVisible = lv.getFirstVisiblePosition() == 0;
+ boolean lastItemVisible = lv.getLastVisiblePosition() == lv.getCount() - 1;
+
+ if (firstItemVisible && lastItemVisible && lv.getChildCount() > 0) {
+ /* Or the first item's top is above or own top */
+ if (lv.getChildAt(0).getTop() < lv.getPaddingTop()) {
+ return true;
+ }
+ /* or the last item's bottom is beyond our own bottom */
+ return lv.getChildAt(lv.getChildCount() - 1).getBottom() >
+ lv.getHeight() - lv.getPaddingBottom();
+ }
+
+ return true;
+ }
+
+ /**
+ * Find the view touching the bottom of this ViewGroup. Non visible children are ignored,
+ * however getChildDrawingOrder is not taking into account for simplicity and because it behaves
+ * inconsistently across platform versions.
+ *
+ * @return View touching the bottom of this ViewGroup or null
+ */
+ @Nullable
+ private static View getBottomView(ViewGroup viewGroup) {
+ if (viewGroup == null || viewGroup.getChildCount() == 0) {
+ return null;
+ }
+ View bottomView = null;
+ for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
+ View child = viewGroup.getChildAt(i);
+ if (child.getVisibility() == View.VISIBLE && child.getBottom() == viewGroup
+ .getMeasuredHeight()) {
+ bottomView = child;
+ break;
+ }
+ }
+ return bottomView;
+ }
+
+ @Nullable
+ private static View getTopView(ViewGroup viewGroup) {
+ if (viewGroup == null || viewGroup.getChildCount() == 0) {
+ return null;
+ }
+ View topView = null;
+ for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
+ View child = viewGroup.getChildAt(i);
+ if (child.getVisibility() == View.VISIBLE && child.getTop() == 0) {
+ topView = child;
+ break;
+ }
+ }
+ return topView;
+ }
+
+ private void init(Context context, AttributeSet attrs, int defStyleAttr) {
+ Resources r = context.getResources();
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MDRootLayout, defStyleAttr, 0);
+ reducePaddingNoTitleNoButtons = a
+ .getBoolean(R.styleable.MDRootLayout_md_reduce_padding_no_title_no_buttons, true);
+ a.recycle();
+
+ noTitlePaddingFull = r.getDimensionPixelSize(R.dimen.md_notitle_vertical_padding);
+ buttonPaddingFull = r.getDimensionPixelSize(R.dimen.md_button_frame_vertical_padding);
+
+ buttonHorizontalEdgeMargin = r.getDimensionPixelSize(R.dimen.md_button_padding_frame_side);
+ buttonBarHeight = r.getDimensionPixelSize(R.dimen.md_button_height);
+
+ dividerPaint = new Paint();
+ dividerWidth = r.getDimensionPixelSize(R.dimen.md_divider_height);
+ dividerPaint.setColor(DialogUtils.resolveColor(context, R.attr.md_divider_color));
+ setWillNotDraw(false);
+ }
+
+ public void setMaxHeight(int maxHeight) {
+ this.maxHeight = maxHeight;
+ }
+
+ public void noTitleNoPadding() {
+ noTitleNoPadding = true;
+ }
+
+ @Override
+ public void onFinishInflate() {
+ super.onFinishInflate();
+ for (int i = 0; i < getChildCount(); i++) {
+ View v = getChildAt(i);
+ if (v.getId() == R.id.md_titleFrame) {
+ titleBar = v;
+ } else if (v.getId() == R.id.md_buttonDefaultNeutral) {
+ buttons[INDEX_NEUTRAL] = (MDButton) v;
+ } else if (v.getId() == R.id.md_buttonDefaultNegative) {
+ buttons[INDEX_NEGATIVE] = (MDButton) v;
+ } else if (v.getId() == R.id.md_buttonDefaultPositive) {
+ buttons[INDEX_POSITIVE] = (MDButton) v;
+ } else {
+ content = v;
+ }
+ }
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (height > maxHeight) {
+ height = maxHeight;
+ }
+
+ useFullPadding = true;
+ boolean hasButtons = false;
+
+ final boolean stacked;
+ if (stackBehavior == StackingBehavior.ALWAYS) {
+ stacked = true;
+ } else if (stackBehavior == StackingBehavior.NEVER) {
+ stacked = false;
+ } else {
+ int buttonsWidth = 0;
+ for (MDButton button : buttons) {
+ if (button != null && isVisible(button)) {
+ button.setStacked(false, false);
+ measureChild(button, widthMeasureSpec, heightMeasureSpec);
+ buttonsWidth += button.getMeasuredWidth();
+ hasButtons = true;
+ }
+ }
+
+ int buttonBarPadding = getContext().getResources()
+ .getDimensionPixelSize(R.dimen.md_neutral_button_margin);
+ final int buttonFrameWidth = width - 2 * buttonBarPadding;
+ stacked = buttonsWidth > buttonFrameWidth;
+ }
+
+ int stackedHeight = 0;
+ isStacked = stacked;
+ if (stacked) {
+ for (MDButton button : buttons) {
+ if (button != null && isVisible(button)) {
+ button.setStacked(true, false);
+ measureChild(button, widthMeasureSpec, heightMeasureSpec);
+ stackedHeight += button.getMeasuredHeight();
+ hasButtons = true;
+ }
+ }
+ }
+
+ int availableHeight = height;
+ int fullPadding = 0;
+ int minPadding = 0;
+ if (hasButtons) {
+ if (isStacked) {
+ availableHeight -= stackedHeight;
+ fullPadding += 2 * buttonPaddingFull;
+ minPadding += 2 * buttonPaddingFull;
+ } else {
+ availableHeight -= buttonBarHeight;
+ fullPadding += 2 * buttonPaddingFull;
+ /* No minPadding */
+ }
+ } else {
+ /* Content has 8dp, we add 16dp and get 24dp, the frame margin */
+ fullPadding += 2 * buttonPaddingFull;
+ }
+
+ if (isVisible(titleBar)) {
+ titleBar.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.UNSPECIFIED);
+ availableHeight -= titleBar.getMeasuredHeight();
+ } else if (!noTitleNoPadding) {
+ fullPadding += noTitlePaddingFull;
+ }
+
+ if (isVisible(content)) {
+ content.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(availableHeight - minPadding, MeasureSpec.AT_MOST));
+
+ if (content.getMeasuredHeight() <= availableHeight - fullPadding) {
+ if (!reducePaddingNoTitleNoButtons || isVisible(titleBar) || hasButtons) {
+ useFullPadding = true;
+ availableHeight -= content.getMeasuredHeight() + fullPadding;
+ } else {
+ useFullPadding = false;
+ availableHeight -= content.getMeasuredHeight() + minPadding;
+ }
+ } else {
+ useFullPadding = false;
+ availableHeight = 0;
+ }
+
+ }
+
+ setMeasuredDimension(width, height - availableHeight);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (content != null) {
+ if (drawTopDivider) {
+ int y = content.getTop();
+ canvas.drawRect(0, y - dividerWidth, getMeasuredWidth(), y, dividerPaint);
+ }
+
+ if (drawBottomDivider) {
+ int y = content.getBottom();
+ canvas.drawRect(0, y, getMeasuredWidth(), y + dividerWidth, dividerPaint);
+ }
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, final int l, int t, final int r, int b) {
+ if (isVisible(titleBar)) {
+ int height = titleBar.getMeasuredHeight();
+ titleBar.layout(l, t, r, t + height);
+ t += height;
+ } else if (!noTitleNoPadding && useFullPadding) {
+ t += noTitlePaddingFull;
+ }
+
+ if (isVisible(content)) {
+ content.layout(l, t, r, t + content.getMeasuredHeight());
+ }
+
+ if (isStacked) {
+ b -= buttonPaddingFull;
+ for (MDButton mButton : buttons) {
+ if (isVisible(mButton)) {
+ mButton.layout(l, b - mButton.getMeasuredHeight(), r, b);
+ b -= mButton.getMeasuredHeight();
+ }
+ }
+ } else {
+ int barTop;
+ int barBottom = b;
+ if (useFullPadding) {
+ barBottom -= buttonPaddingFull;
+ }
+ barTop = barBottom - buttonBarHeight;
+ /* START:
+ Neutral Negative Positive
+
+ CENTER:
+ Negative Neutral Positive
+
+ END:
+ Positive Negative Neutral
+
+ (With no Positive, Negative takes it's place except for CENTER)
+ */
+ int offset = buttonHorizontalEdgeMargin;
+
+ /* Used with CENTER gravity */
+ int neutralLeft = -1;
+ int neutralRight = -1;
+
+ if (isVisible(buttons[INDEX_POSITIVE])) {
+ int bl, br;
+ if (buttonGravity == GravityEnum.END) {
+ bl = l + offset;
+ br = bl + buttons[INDEX_POSITIVE].getMeasuredWidth();
+ } else { /* START || CENTER */
+ br = r - offset;
+ bl = br - buttons[INDEX_POSITIVE].getMeasuredWidth();
+ neutralRight = bl;
+ }
+ buttons[INDEX_POSITIVE].layout(bl, barTop, br, barBottom);
+ offset += buttons[INDEX_POSITIVE].getMeasuredWidth();
+ }
+
+ if (isVisible(buttons[INDEX_NEGATIVE])) {
+ int bl, br;
+ if (buttonGravity == GravityEnum.END) {
+ bl = l + offset;
+ br = bl + buttons[INDEX_NEGATIVE].getMeasuredWidth();
+ } else if (buttonGravity == GravityEnum.START) {
+ br = r - offset;
+ bl = br - buttons[INDEX_NEGATIVE].getMeasuredWidth();
+ } else { /* CENTER */
+ bl = l + buttonHorizontalEdgeMargin;
+ br = bl + buttons[INDEX_NEGATIVE].getMeasuredWidth();
+ neutralLeft = br;
+ }
+ buttons[INDEX_NEGATIVE].layout(bl, barTop, br, barBottom);
+ }
+
+ if (isVisible(buttons[INDEX_NEUTRAL])) {
+ int bl, br;
+ if (buttonGravity == GravityEnum.END) {
+ br = r - buttonHorizontalEdgeMargin;
+ bl = br - buttons[INDEX_NEUTRAL].getMeasuredWidth();
+ } else if (buttonGravity == GravityEnum.START) {
+ bl = l + buttonHorizontalEdgeMargin;
+ br = bl + buttons[INDEX_NEUTRAL].getMeasuredWidth();
+ } else { /* CENTER */
+ if (neutralLeft == -1 && neutralRight != -1) {
+ neutralLeft = neutralRight - buttons[INDEX_NEUTRAL].getMeasuredWidth();
+ } else if (neutralRight == -1 && neutralLeft != -1) {
+ neutralRight = neutralLeft + buttons[INDEX_NEUTRAL].getMeasuredWidth();
+ } else if (neutralRight == -1) {
+ neutralLeft = (r - l) / 2 - buttons[INDEX_NEUTRAL].getMeasuredWidth() / 2;
+ neutralRight = neutralLeft + buttons[INDEX_NEUTRAL].getMeasuredWidth();
+ }
+ bl = neutralLeft;
+ br = neutralRight;
+ }
+
+ buttons[INDEX_NEUTRAL].layout(bl, barTop, br, barBottom);
+ }
+ }
+
+ setUpDividersVisibility(content, true, true);
+ }
+
+ public void setStackingBehavior(StackingBehavior behavior) {
+ stackBehavior = behavior;
+ invalidate();
+ }
+
+ public void setDividerColor(int color) {
+ dividerPaint.setColor(color);
+ invalidate();
+ }
+
+ public void setButtonGravity(GravityEnum gravity) {
+ buttonGravity = gravity;
+ invertGravityIfNecessary();
+ }
+
+ private void invertGravityIfNecessary() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return;
+ }
+ Configuration config = getResources().getConfiguration();
+ if (config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ switch (buttonGravity) {
+ case START:
+ buttonGravity = GravityEnum.END;
+ break;
+ case END:
+ buttonGravity = GravityEnum.START;
+ break;
+ }
+ }
+ }
+
+ public void setButtonStackedGravity(GravityEnum gravity) {
+ for (MDButton mButton : buttons) {
+ if (mButton != null) {
+ mButton.setStackedGravity(gravity);
+ }
+ }
+ }
+
+ private void setUpDividersVisibility(final View view, final boolean setForTop,
+ final boolean setForBottom) {
+ if (view == null) {
+ return;
+ }
+ if (view instanceof ScrollView) {
+ final ScrollView sv = (ScrollView) view;
+ if (canScrollViewScroll(sv)) {
+ addScrollListener(sv, setForTop, setForBottom);
+ } else {
+ if (setForTop) {
+ drawTopDivider = false;
+ }
+ if (setForBottom) {
+ drawBottomDivider = false;
+ }
+ }
+ } else if (view instanceof AdapterView) {
+ final AdapterView sv = (AdapterView) view;
+ if (canAdapterViewScroll(sv)) {
+ addScrollListener(sv, setForTop, setForBottom);
+ } else {
+ if (setForTop) {
+ drawTopDivider = false;
+ }
+ if (setForBottom) {
+ drawBottomDivider = false;
+ }
+ }
+ } else if (view instanceof WebView) {
+ view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ if (view.getMeasuredHeight() != 0) {
+ if (!canWebViewScroll((WebView) view)) {
+ if (setForTop) {
+ drawTopDivider = false;
+ }
+ if (setForBottom) {
+ drawBottomDivider = false;
+ }
+ } else {
+ addScrollListener((ViewGroup) view, setForTop, setForBottom);
+ }
+ view.getViewTreeObserver().removeOnPreDrawListener(this);
+ }
+ return true;
+ }
+ });
+ } else if (view instanceof RecyclerView) {
+ boolean canScroll = canRecyclerViewScroll((RecyclerView) view);
+ if (setForTop) {
+ drawTopDivider = canScroll;
+ }
+ if (setForBottom) {
+ drawBottomDivider = canScroll;
+ }
+ if (canScroll) {
+ addScrollListener((ViewGroup) view, setForTop, setForBottom);
+ }
+ } else if (view instanceof ViewGroup) {
+ View topView = getTopView((ViewGroup) view);
+ setUpDividersVisibility(topView, setForTop, setForBottom);
+ View bottomView = getBottomView((ViewGroup) view);
+ if (bottomView != topView) {
+ setUpDividersVisibility(bottomView, false, true);
+ }
+ }
+ }
+
+ private void addScrollListener(final ViewGroup vg, final boolean setForTop,
+ final boolean setForBottom) {
+ if ((!setForBottom && topOnScrollChangedListener == null
+ || (setForBottom && bottomOnScrollChangedListener == null))) {
+ if (vg instanceof RecyclerView) {
+ RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+ boolean hasButtons = false;
+ for (MDButton button : buttons) {
+ if (button != null && button.getVisibility() != View.GONE) {
+ hasButtons = true;
+ break;
+ }
+ }
+ invalidateDividersForScrollingView(vg, setForTop, setForBottom, hasButtons);
+ invalidate();
+ }
+ };
+ ((RecyclerView) vg).addOnScrollListener(scrollListener);
+ scrollListener.onScrolled((RecyclerView) vg, 0, 0);
+ } else {
+ ViewTreeObserver.OnScrollChangedListener onScrollChangedListener = new ViewTreeObserver.OnScrollChangedListener() {
+ @Override
+ public void onScrollChanged() {
+ boolean hasButtons = false;
+ for (MDButton button : buttons) {
+ if (button != null && button.getVisibility() != View.GONE) {
+ hasButtons = true;
+ break;
+ }
+ }
+ if (vg instanceof WebView) {
+ invalidateDividersForWebView((WebView) vg, setForTop, setForBottom, hasButtons);
+ } else {
+ invalidateDividersForScrollingView(vg, setForTop, setForBottom, hasButtons);
+ }
+ invalidate();
+ }
+ };
+ if (!setForBottom) {
+ topOnScrollChangedListener = onScrollChangedListener;
+ vg.getViewTreeObserver().addOnScrollChangedListener(topOnScrollChangedListener);
+ } else {
+ bottomOnScrollChangedListener = onScrollChangedListener;
+ vg.getViewTreeObserver().addOnScrollChangedListener(bottomOnScrollChangedListener);
+ }
+ onScrollChangedListener.onScrollChanged();
+ }
+ }
+ }
+
+ private void invalidateDividersForScrollingView(ViewGroup view, final boolean setForTop,
+ boolean setForBottom, boolean hasButtons) {
+ if (setForTop && view.getChildCount() > 0) {
+ drawTopDivider = titleBar != null &&
+ titleBar.getVisibility() != View.GONE &&
+ //Not scrolled to the top.
+ view.getScrollY() + view.getPaddingTop() > view.getChildAt(0).getTop();
+
+ }
+ if (setForBottom && view.getChildCount() > 0) {
+ drawBottomDivider = hasButtons &&
+ view.getScrollY() + view.getHeight() - view.getPaddingBottom() < view
+ .getChildAt(view.getChildCount() - 1).getBottom();
+ }
+ }
+
+ private void invalidateDividersForWebView(WebView view, final boolean setForTop,
+ boolean setForBottom, boolean hasButtons) {
+ if (setForTop) {
+ drawTopDivider = titleBar != null &&
+ titleBar.getVisibility() != View.GONE &&
+ //Not scrolled to the top.
+ view.getScrollY() + view.getPaddingTop() > 0;
+ }
+ if (setForBottom) {
+ //noinspection deprecation
+ drawBottomDivider = hasButtons &&
+ view.getScrollY() + view.getMeasuredHeight() - view.getPaddingBottom()
+ < view.getContentHeight() * view.getScale();
+ }
+ }
+}
\ No newline at end of file
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/internal/MDTintHelper.java b/widget/src/main/java/com/hzecool/widget/materialdialog/internal/MDTintHelper.java
new file mode 100644
index 0000000..3410332
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/internal/MDTintHelper.java
@@ -0,0 +1,194 @@
+package com.hzecool.widget.materialdialog.internal;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v7.widget.AppCompatEditText;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.ProgressBar;
+import android.widget.RadioButton;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.hzecool.widget.R;
+import com.hzecool.widget.materialdialog.util.DialogUtils;
+
+import java.lang.reflect.Field;
+
+/**
+ * Tints widgets
+ */
+public class MDTintHelper {
+
+ public static void setTint(@NonNull RadioButton radioButton,
+ @NonNull ColorStateList colors) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ radioButton.setButtonTintList(colors);
+ } else {
+ Drawable radioDrawable = ContextCompat.getDrawable(radioButton.getContext(),
+ R.drawable.abc_btn_radio_material);
+ Drawable d = DrawableCompat.wrap(radioDrawable);
+ DrawableCompat.setTintList(d, colors);
+ radioButton.setButtonDrawable(d);
+ }
+ }
+
+ public static void setTint(@NonNull RadioButton radioButton,
+ @ColorInt int color) {
+ final int disabledColor = DialogUtils.getDisabledColor(radioButton.getContext());
+ ColorStateList sl = new ColorStateList(new int[][]{
+ new int[]{android.R.attr.state_enabled, -android.R.attr.state_checked},
+ new int[]{android.R.attr.state_enabled, android.R.attr.state_checked},
+ new int[]{-android.R.attr.state_enabled, -android.R.attr.state_checked},
+ new int[]{-android.R.attr.state_enabled, android.R.attr.state_checked}
+ }, new int[]{
+ DialogUtils.resolveColor(radioButton.getContext(), R.attr.colorControlNormal),
+ color,
+ disabledColor,
+ disabledColor
+ });
+ setTint(radioButton, sl);
+ }
+
+ public static void setTint(@NonNull CheckBox box,
+ @NonNull ColorStateList colors) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ box.setButtonTintList(colors);
+ } else {
+ Drawable checkDrawable = ContextCompat.getDrawable(box.getContext(),
+ R.drawable.abc_btn_check_material);
+ Drawable drawable = DrawableCompat.wrap(checkDrawable);
+ DrawableCompat.setTintList(drawable, colors);
+ box.setButtonDrawable(drawable);
+ }
+ }
+
+ public static void setTint(@NonNull CheckBox box, @ColorInt int color) {
+ final int disabledColor = DialogUtils.getDisabledColor(box.getContext());
+ ColorStateList sl = new ColorStateList(new int[][]{
+ new int[]{android.R.attr.state_enabled, -android.R.attr.state_checked},
+ new int[]{android.R.attr.state_enabled, android.R.attr.state_checked},
+ new int[]{-android.R.attr.state_enabled, -android.R.attr.state_checked},
+ new int[]{-android.R.attr.state_enabled, android.R.attr.state_checked}
+ }, new int[]{
+ DialogUtils.resolveColor(box.getContext(), R.attr.colorControlNormal),
+ color,
+ disabledColor,
+ disabledColor
+ });
+ setTint(box, sl);
+ }
+
+ public static void setTint(@NonNull SeekBar seekBar, @ColorInt int color) {
+ ColorStateList s1 = ColorStateList.valueOf(color);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ seekBar.setThumbTintList(s1);
+ seekBar.setProgressTintList(s1);
+ } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) {
+ Drawable progressDrawable = DrawableCompat.wrap(seekBar.getProgressDrawable());
+ seekBar.setProgressDrawable(progressDrawable);
+ DrawableCompat.setTintList(progressDrawable, s1);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ Drawable thumbDrawable = DrawableCompat.wrap(seekBar.getThumb());
+ DrawableCompat.setTintList(thumbDrawable, s1);
+ seekBar.setThumb(thumbDrawable);
+ }
+ } else {
+ PorterDuff.Mode mode = PorterDuff.Mode.SRC_IN;
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
+ mode = PorterDuff.Mode.MULTIPLY;
+ }
+ if (seekBar.getIndeterminateDrawable() != null) {
+ seekBar.getIndeterminateDrawable().setColorFilter(color, mode);
+ }
+ if (seekBar.getProgressDrawable() != null) {
+ seekBar.getProgressDrawable().setColorFilter(color, mode);
+ }
+ }
+ }
+
+ public static void setTint(@NonNull ProgressBar progressBar,
+ @ColorInt int color) {
+ setTint(progressBar, color, false);
+ }
+
+ private static void setTint(@NonNull ProgressBar progressBar,
+ @ColorInt int color,
+ boolean skipIndeterminate) {
+ ColorStateList sl = ColorStateList.valueOf(color);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ progressBar.setProgressTintList(sl);
+ progressBar.setSecondaryProgressTintList(sl);
+ if (!skipIndeterminate) {
+ progressBar.setIndeterminateTintList(sl);
+ }
+ } else {
+ PorterDuff.Mode mode = PorterDuff.Mode.SRC_IN;
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
+ mode = PorterDuff.Mode.MULTIPLY;
+ }
+ if (!skipIndeterminate && progressBar.getIndeterminateDrawable() != null) {
+ progressBar.getIndeterminateDrawable().setColorFilter(color, mode);
+ }
+ if (progressBar.getProgressDrawable() != null) {
+ progressBar.getProgressDrawable().setColorFilter(color, mode);
+ }
+ }
+ }
+
+ private static ColorStateList createEditTextColorStateList(
+ @NonNull Context context, @ColorInt int color) {
+ int[][] states = new int[3][];
+ int[] colors = new int[3];
+ int i = 0;
+ states[i] = new int[]{-android.R.attr.state_enabled};
+ colors[i] = DialogUtils.resolveColor(context, R.attr.colorControlNormal);
+ i++;
+ states[i] = new int[]{-android.R.attr.state_pressed, -android.R.attr.state_focused};
+ colors[i] = DialogUtils.resolveColor(context, R.attr.colorControlNormal);
+ i++;
+ states[i] = new int[]{};
+ colors[i] = color;
+ return new ColorStateList(states, colors);
+ }
+
+ public static void setTint(@NonNull EditText editText, @ColorInt int color) {
+ ColorStateList editTextColorStateList = createEditTextColorStateList(editText.getContext(),
+ color);
+ if (editText instanceof AppCompatEditText) {
+ ((AppCompatEditText) editText).setSupportBackgroundTintList(editTextColorStateList);
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ editText.setBackgroundTintList(editTextColorStateList);
+ }
+ setCursorTint(editText, color);
+ }
+
+ private static void setCursorTint(@NonNull EditText editText, @ColorInt int color) {
+ try {
+ Field fCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes");
+ fCursorDrawableRes.setAccessible(true);
+ int mCursorDrawableRes = fCursorDrawableRes.getInt(editText);
+ Field fEditor = TextView.class.getDeclaredField("mEditor");
+ fEditor.setAccessible(true);
+ Object editor = fEditor.get(editText);
+ Class> clazz = editor.getClass();
+ Field fCursorDrawable = clazz.getDeclaredField("mCursorDrawable");
+ fCursorDrawable.setAccessible(true);
+ Drawable[] drawables = new Drawable[2];
+ drawables[0] = ContextCompat.getDrawable(editText.getContext(), mCursorDrawableRes);
+ drawables[1] = ContextCompat.getDrawable(editText.getContext(), mCursorDrawableRes);
+ drawables[0].setColorFilter(color, PorterDuff.Mode.SRC_IN);
+ drawables[1].setColorFilter(color, PorterDuff.Mode.SRC_IN);
+ fCursorDrawable.set(editor, drawables);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/internal/ThemeSingleton.java b/widget/src/main/java/com/hzecool/widget/materialdialog/internal/ThemeSingleton.java
new file mode 100644
index 0000000..631d695
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/internal/ThemeSingleton.java
@@ -0,0 +1,62 @@
+package com.hzecool.widget.materialdialog.internal;
+
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.annotation.DrawableRes;
+
+import com.hzecool.widget.materialdialog.GravityEnum;
+
+
+/**
+ * Use of this is discouraged for now; for internal use only. See the Global Theming section of the
+ * README.
+ */
+public class ThemeSingleton {
+
+ private static ThemeSingleton singleton;
+ public boolean darkTheme = false;
+ @ColorInt
+ public int titleColor = 0;
+ @ColorInt
+ public int contentColor = 0;
+ public ColorStateList positiveColor = null;
+ public ColorStateList neutralColor = null;
+ public ColorStateList negativeColor = null;
+ @ColorInt
+ public int widgetColor = 0;
+ @ColorInt
+ public int itemColor = 0;
+ public Drawable icon = null;
+ @ColorInt
+ public int backgroundColor = 0;
+ @ColorInt
+ public int dividerColor = 0;
+ public ColorStateList linkColor = null;
+ @DrawableRes
+ public int listSelector = 0;
+ @DrawableRes
+ public int btnSelectorStacked = 0;
+ @DrawableRes
+ public int btnSelectorPositive = 0;
+ @DrawableRes
+ public int btnSelectorNeutral = 0;
+ @DrawableRes
+ public int btnSelectorNegative = 0;
+ public GravityEnum titleGravity = GravityEnum.START;
+ public GravityEnum contentGravity = GravityEnum.START;
+ public GravityEnum btnStackedGravity = GravityEnum.END;
+ public GravityEnum itemsGravity = GravityEnum.START;
+ public GravityEnum buttonsGravity = GravityEnum.START;
+
+ public static ThemeSingleton get(boolean createIfNull) {
+ if (singleton == null && createIfNull) {
+ singleton = new ThemeSingleton();
+ }
+ return singleton;
+ }
+
+ public static ThemeSingleton get() {
+ return get(true);
+ }
+}
\ No newline at end of file
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/util/DialogUtils.java b/widget/src/main/java/com/hzecool/widget/materialdialog/util/DialogUtils.java
new file mode 100644
index 0000000..3627b55
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/util/DialogUtils.java
@@ -0,0 +1,305 @@
+package com.hzecool.widget.materialdialog.util;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.IBinder;
+import android.support.annotation.ArrayRes;
+import android.support.annotation.AttrRes;
+import android.support.annotation.ColorInt;
+import android.support.annotation.ColorRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+
+import com.hzecool.widget.materialdialog.GravityEnum;
+import com.hzecool.widget.materialdialog.MaterialDialog;
+
+
+/**
+ * @author Aidan Follestad (afollestad)
+ */
+public class DialogUtils {
+
+// @SuppressWarnings("ConstantConditions")
+// public static float resolveFloat(Context context, int attr) {
+// TypedArray a = context.obtainStyledAttributes(null, new int[]{attr});
+// try {
+// return a.getFloat(0, 0);
+// } finally {
+// a.recycle();
+// }
+// }
+
+ @ColorInt
+ public static int getDisabledColor(Context context) {
+ final int primaryColor = resolveColor(context, android.R.attr.textColorPrimary);
+ final int disabledColor = isColorDark(primaryColor) ? Color.BLACK : Color.WHITE;
+ return adjustAlpha(disabledColor, 0.3f);
+ }
+
+ @ColorInt
+ public static int adjustAlpha(@ColorInt int color,
+ @SuppressWarnings("SameParameterValue") float factor) {
+ int alpha = Math.round(Color.alpha(color) * factor);
+ int red = Color.red(color);
+ int green = Color.green(color);
+ int blue = Color.blue(color);
+ return Color.argb(alpha, red, green, blue);
+ }
+
+ @ColorInt
+ public static int resolveColor(Context context, @AttrRes int attr) {
+ return resolveColor(context, attr, 0);
+ }
+
+ @ColorInt
+ public static int resolveColor(Context context, @AttrRes int attr, int fallback) {
+ TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
+ try {
+ return a.getColor(0, fallback);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ // Try to resolve the colorAttr attribute.
+ public static ColorStateList resolveActionTextColorStateList(
+ Context context, @AttrRes int colorAttr, ColorStateList fallback) {
+ TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{colorAttr});
+ try {
+ final TypedValue value = a.peekValue(0);
+ if (value == null) {
+ return fallback;
+ }
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
+ && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ return getActionTextStateList(context, value.data);
+ } else {
+ final ColorStateList stateList = a.getColorStateList(0);
+ if (stateList != null) {
+ return stateList;
+ } else {
+ return fallback;
+ }
+ }
+ } finally {
+ a.recycle();
+ }
+ }
+
+ // Get the specified color resource, creating a ColorStateList if the resource
+ // points to a color value.
+ public static ColorStateList getActionTextColorStateList(Context context, @ColorRes int colorId) {
+ final TypedValue value = new TypedValue();
+ context.getResources().getValue(colorId, value, true);
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
+ && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ return getActionTextStateList(context, value.data);
+ } else {
+
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
+ //noinspection deprecation
+ return context.getResources().getColorStateList(colorId);
+ } else {
+ return context.getColorStateList(colorId);
+ }
+ }
+ }
+
+ /**
+ * Returns a color associated with a particular resource ID
+ * Starting in {@link Build.VERSION_CODES#M}, the returned
+ * color will be styled for the specified Context's theme.
+ *
+ * @param colorId The desired resource identifier, as generated by the aapt tool. This integer
+ * encodes the package, type, and resource entry. The value 0 is an invalid identifier.
+ * @return A single color value in the form 0xAARRGGBB.
+ */
+ @ColorInt
+ public static int getColor(Context context, @ColorRes int colorId) {
+ return ContextCompat.getColor(context, colorId);
+ }
+
+ public static String resolveString(Context context, @AttrRes int attr) {
+ TypedValue v = new TypedValue();
+ context.getTheme().resolveAttribute(attr, v, true);
+ return (String) v.string;
+ }
+
+ private static int gravityEnumToAttrInt(GravityEnum value) {
+ switch (value) {
+ case CENTER:
+ return 1;
+ case END:
+ return 2;
+ default:
+ return 0;
+ }
+ }
+
+ public static GravityEnum resolveGravityEnum(Context context,
+ @AttrRes int attr,
+ GravityEnum defaultGravity) {
+ TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
+ try {
+ switch (a.getInt(0, gravityEnumToAttrInt(defaultGravity))) {
+ case 1:
+ return GravityEnum.CENTER;
+ case 2:
+ return GravityEnum.END;
+ default:
+ return GravityEnum.START;
+ }
+ } finally {
+ a.recycle();
+ }
+ }
+
+ public static Drawable resolveDrawable(Context context, @AttrRes int attr) {
+ return resolveDrawable(context, attr, null);
+ }
+
+ private static Drawable resolveDrawable(Context context,
+ @AttrRes int attr,
+ @SuppressWarnings(
+ "SameParameterValue") Drawable fallback) {
+ TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
+ try {
+ Drawable d = a.getDrawable(0);
+ if (d == null && fallback != null) {
+ d = fallback;
+ }
+ return d;
+ } finally {
+ a.recycle();
+ }
+ }
+
+ public static int resolveDimension(Context context, @AttrRes int attr) {
+ return resolveDimension(context, attr, -1);
+ }
+
+ private static int resolveDimension(Context context, @AttrRes int attr, int fallback) {
+ TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
+ try {
+ return a.getDimensionPixelSize(0, fallback);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ public static boolean resolveBoolean(Context context, @AttrRes int attr, boolean fallback) {
+ TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
+ try {
+ return a.getBoolean(0, fallback);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ public static boolean resolveBoolean(Context context, @AttrRes int attr) {
+ return resolveBoolean(context, attr, false);
+ }
+
+ public static boolean isColorDark(@ColorInt int color) {
+ double darkness = 1
+ - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
+ return darkness >= 0.5;
+ }
+
+ public static void setBackgroundCompat(View view, Drawable d) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+ //noinspection deprecation
+ view.setBackgroundDrawable(d);
+ } else {
+ view.setBackground(d);
+ }
+ }
+
+ public static void showKeyboard(@NonNull final DialogInterface di,
+ @NonNull final MaterialDialog.Builder builder) {
+ final MaterialDialog dialog = (MaterialDialog) di;
+ if (dialog.getInputEditText() == null) {
+ return;
+ }
+ dialog.getInputEditText().post(new Runnable() {
+ @Override
+ public void run() {
+ dialog.getInputEditText().requestFocus();
+ InputMethodManager imm = (InputMethodManager) builder.getContext()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (imm != null) {
+ imm.showSoftInput(dialog.getInputEditText(), InputMethodManager.SHOW_IMPLICIT);
+ }
+ }
+ });
+ }
+
+ public static void hideKeyboard(@NonNull final DialogInterface di,
+ @NonNull final MaterialDialog.Builder builder) {
+ final MaterialDialog dialog = (MaterialDialog) di;
+ if (dialog.getInputEditText() == null) {
+ return;
+ }
+ InputMethodManager imm = (InputMethodManager) builder.getContext()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (imm != null) {
+ final View currentFocus = dialog.getCurrentFocus();
+ final IBinder windowToken = currentFocus != null ?
+ currentFocus.getWindowToken() : dialog.getView().getWindowToken();
+ if (windowToken != null) {
+ imm.hideSoftInputFromWindow(windowToken, 0);
+ }
+ }
+ }
+
+ public static ColorStateList getActionTextStateList(Context context, int newPrimaryColor) {
+ final int fallBackButtonColor = DialogUtils
+ .resolveColor(context, android.R.attr.textColorPrimary);
+ if (newPrimaryColor == 0) {
+ newPrimaryColor = fallBackButtonColor;
+ }
+ int[][] states = new int[][]{
+ new int[]{-android.R.attr.state_enabled}, // disabled
+ new int[]{} // enabled
+ };
+ int[] colors = new int[]{
+ DialogUtils.adjustAlpha(newPrimaryColor, 0.4f),
+ newPrimaryColor
+ };
+ return new ColorStateList(states, colors);
+ }
+
+ public static int[] getColorArray(@NonNull Context context, @ArrayRes int array) {
+ if (array == 0) {
+ return null;
+ }
+ TypedArray ta = context.getResources().obtainTypedArray(array);
+ int[] colors = new int[ta.length()];
+ for (int i = 0; i < ta.length(); i++) {
+ colors[i] = ta.getColor(i, 0);
+ }
+ ta.recycle();
+ return colors;
+ }
+
+ public static boolean isIn(@NonNull T find, @Nullable T[] ary) {
+ if (ary == null || ary.length == 0) {
+ return false;
+ }
+ for (T item : ary) {
+ if (item.equals(find)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/util/RippleHelper.java b/widget/src/main/java/com/hzecool/widget/materialdialog/util/RippleHelper.java
new file mode 100644
index 0000000..6fa42db
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/util/RippleHelper.java
@@ -0,0 +1,21 @@
+package com.hzecool.widget.materialdialog.util;
+
+import android.annotation.TargetApi;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.RippleDrawable;
+import android.os.Build;
+import android.support.annotation.ColorInt;
+
+/**
+ * @author Aidan Follestad (afollestad)
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class RippleHelper {
+
+ public static void applyColor(Drawable d, @ColorInt int color) {
+ if (d instanceof RippleDrawable) {
+ ((RippleDrawable) d).setColor(ColorStateList.valueOf(color));
+ }
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/materialdialog/util/TypefaceHelper.java b/widget/src/main/java/com/hzecool/widget/materialdialog/util/TypefaceHelper.java
new file mode 100644
index 0000000..c521f48
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/materialdialog/util/TypefaceHelper.java
@@ -0,0 +1,47 @@
+package com.hzecool.widget.materialdialog.util;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.support.v4.util.SimpleArrayMap;
+
+/*
+ Each call to Typeface.createFromAsset will load a new instance of the typeface into memory,
+ and this memory is not consistently get garbage collected
+ http://code.google.com/p/android/issues/detail?id=9904
+ (It states released but even on Lollipop you can see the typefaces accumulate even after
+ multiple GC passes)
+
+ You can detect this by running:
+ adb shell dumpsys meminfo com.your.packagenage
+
+ You will see output like:
+
+ Asset Allocations
+ zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
+ zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
+ zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
+ zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Regular.ttf: 123K
+ zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
+
+*/
+public class TypefaceHelper {
+
+ private static final SimpleArrayMap cache = new SimpleArrayMap<>();
+
+ public static Typeface get(Context c, String name) {
+ synchronized (cache) {
+ if (!cache.containsKey(name)) {
+ try {
+ Typeface t = Typeface.createFromAsset(
+ c.getAssets(), String.format("fonts/%s", name));
+ cache.put(name, t);
+ return t;
+ } catch (RuntimeException e) {
+ return null;
+ }
+ }
+ return cache.get(name);
+ }
+ }
+}
+
diff --git a/widget/src/main/java/com/hzecool/widget/ninegridview/ColorFilterImageView.java b/widget/src/main/java/com/hzecool/widget/ninegridview/ColorFilterImageView.java
new file mode 100644
index 0000000..6909c68
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/ninegridview/ColorFilterImageView.java
@@ -0,0 +1,51 @@
+package com.hzecool.widget.ninegridview;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.PorterDuff.Mode;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.widget.ImageView;
+
+/**
+ * @author hnclca
+ * @ClassName: ColorFilterImageView
+ * 实现图像根据按下抬起动作变化颜色
+ * @date 2016-02-26
+ */
+public class ColorFilterImageView extends android.support.v7.widget.AppCompatImageView implements OnTouchListener {
+ public ColorFilterImageView(Context context) {
+ this(context, null, 0);
+ }
+
+ public ColorFilterImageView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ColorFilterImageView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ private void init() {
+ setOnTouchListener(this);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN: // 按下时图像变灰
+ setColorFilter(Color.GRAY, Mode.MULTIPLY);
+ break;
+ case MotionEvent.ACTION_UP: // 手指离开或取消操作时恢复原色
+ case MotionEvent.ACTION_CANCEL:
+ setColorFilter(Color.TRANSPARENT);
+ break;
+ default:
+ break;
+ }
+ return false;
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/ninegridview/DisplayUtils.java b/widget/src/main/java/com/hzecool/widget/ninegridview/DisplayUtils.java
new file mode 100644
index 0000000..0f3a2d0
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/ninegridview/DisplayUtils.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2016 venshine.cn@gmail.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.hzecool.widget.ninegridview;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.Window;
+
+/**
+ * 屏幕显示相关信息
+ *
+ * @author venshine
+ */
+public class DisplayUtils {
+
+ /**
+ * 是否横屏
+ *
+ * @param context
+ * @return
+ */
+ public static boolean isLandscape(Context context) {
+ return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
+ }
+
+ /**
+ * 是否竖屏
+ *
+ * @param context
+ * @return
+ */
+ public static boolean isPortrait(Context context) {
+ return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
+ }
+
+ /**
+ * Get screen width, in pixels
+ *
+ * @param context
+ * @return
+ */
+ public static int getScreenWidth(Context context) {
+ DisplayMetrics dm = context.getResources().getDisplayMetrics();
+ return dm.widthPixels;
+ }
+
+ /**
+ * Get screen height, in pixels
+ *
+ * @param context
+ * @return
+ */
+ public static int getScreenHeight(Context context) {
+ DisplayMetrics dm = context.getResources().getDisplayMetrics();
+ return dm.heightPixels;
+ }
+
+ /**
+ * Get screen density, the logical density of the display
+ *
+ * @param context
+ * @return
+ */
+ public static float getScreenDensity(Context context) {
+ DisplayMetrics dm = context.getResources().getDisplayMetrics();
+ return dm.density;
+ }
+
+ /**
+ * Get screen density dpi, the screen density expressed as dots-per-inch
+ *
+ * @param context
+ * @return
+ */
+ public static int getScreenDensityDPI(Context context) {
+ DisplayMetrics dm = context.getResources().getDisplayMetrics();
+ return dm.densityDpi;
+ }
+
+ /**
+ * Get titlebar height, this method cannot be used in onCreate(),onStart(),onResume(), unless it is called in the
+ * post(Runnable).
+ *
+ * @param activity
+ * @return
+ */
+ public static int getTitleBarHeight(Activity activity) {
+ int statusBarHeight = getStatusBarHeight(activity);
+ int contentViewTop = activity.getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();
+ int titleBarHeight = contentViewTop - statusBarHeight;
+ return titleBarHeight < 0 ? 0 : titleBarHeight;
+ }
+
+ /**
+ * Get statusbar height, this method cannot be used in onCreate(),onStart(),onResume(), unless it is called in the
+ * post(Runnable).
+ *
+ * @param activity
+ * @return
+ */
+ public static int getStatusBarHeight(Activity activity) {
+ Rect rect = new Rect();
+ activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
+ return rect.top;
+ }
+
+ /**
+ * Get statusbar height
+ *
+ * @param activity
+ * @return
+ */
+ public static int getStatusBarHeight2(Activity activity) {
+ int statusBarHeight = getStatusBarHeight(activity);
+ if (0 == statusBarHeight) {
+ Class> localClass;
+ try {
+ localClass = Class.forName("com.android.internal.R$dimen");
+ Object localObject = localClass.newInstance();
+ int id = Integer.parseInt(localClass.getField("status_bar_height").get(localObject).toString());
+ statusBarHeight = activity.getResources().getDimensionPixelSize(id);
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ } catch (NoSuchFieldException e) {
+ e.printStackTrace();
+ }
+ }
+ return statusBarHeight;
+ }
+
+ /**
+ * Convert dp to px by the density of phone
+ *
+ * @param context
+ * @param dp
+ * @return
+ */
+ public static int dip2px(Context context, float dp) {
+ if (context == null) {
+ return -1;
+ }
+ return (int) (dipToPx(context, dp) + 0.5f);
+ }
+
+ /**
+ * Convert dp to px
+ *
+ * @param context
+ * @param dp
+ * @return
+ */
+ private static float dipToPx(Context context, float dp) {
+ if (context == null) {
+ return -1;
+ }
+ float scale = context.getResources().getDisplayMetrics().density;
+ return dp * scale;
+ }
+
+ /**
+ * Convert px to dp by the density of phone
+ *
+ * @param context
+ * @param px
+ * @return
+ */
+ public static int px2dip(Context context, float px) {
+ if (context == null) {
+ return -1;
+ }
+ return (int) (pxToDip(context, px) + 0.5f);
+ }
+
+ /**
+ * Convert px to dp
+ *
+ * @param context
+ * @param px
+ * @return
+ */
+ private static float pxToDip(Context context, float px) {
+ if (context == null) {
+ return -1;
+ }
+ float scale = context.getResources().getDisplayMetrics().density;
+ return px / scale;
+ }
+
+ /**
+ * Convert px to sp
+ *
+ * @param context
+ * @param px
+ * @return
+ */
+ public static int px2sp(Context context, float px) {
+ return (int) (pxToSp(context, px) + 0.5f);
+ }
+
+ /**
+ * Convert px to sp
+ *
+ * @param context
+ * @param px
+ * @return
+ */
+ private static float pxToSp(Context context, float px) {
+ float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+ return px / fontScale;
+ }
+
+ /**
+ * Convert sp to px
+ *
+ * @param context
+ * @param sp
+ * @return
+ */
+ public static int sp2px(Context context, float sp) {
+ return (int) (spToPx(context, sp) + 0.5f);
+ }
+
+ /**
+ * Convert sp to px
+ *
+ * @param context
+ * @param sp
+ * @return
+ */
+ private static float spToPx(Context context, float sp) {
+ float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+ return sp * fontScale;
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/ninegridview/MultiImageView.java b/widget/src/main/java/com/hzecool/widget/ninegridview/MultiImageView.java
new file mode 100644
index 0000000..98e9619
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/ninegridview/MultiImageView.java
@@ -0,0 +1,214 @@
+package com.hzecool.widget.ninegridview;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.hzecool.widget.utils.GlideSetting;
+
+import java.util.List;
+
+/**
+ * Created by song on 2017/8/7.
+ */
+
+public class MultiImageView extends LinearLayout {
+ public static int MAX_WIDTH = 0;
+
+ // 照片的Url列表
+ private List imagesList;
+
+ /**
+ * 长度 单位为Pixel
+ **/
+ private int pxOneMaxWandH; // 单张图最大允许宽高
+ private int pxMoreWandH = 0;// 多张图的宽高
+ private int pxImagePadding = DisplayUtils.dip2px(getContext(), 3);// 图片间的间距
+
+ private int MAX_PER_ROW_COUNT = 3;// 每行显示最大数
+
+ private LayoutParams onePicPara;
+ private LayoutParams morePara, moreParaColumnFirst;
+ private LayoutParams rowPara;
+
+ private OnItemClickListener mOnItemClickListener;
+
+ public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
+ mOnItemClickListener = onItemClickListener;
+ }
+
+ public MultiImageView(Context context) {
+ super(context);
+ }
+
+ public MultiImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setList(List lists) throws IllegalArgumentException {
+ if (lists == null) {
+ throw new IllegalArgumentException("imageList is null...");
+ }
+ imagesList = lists;
+
+ if (MAX_WIDTH > 0) {
+ pxMoreWandH = (MAX_WIDTH - pxImagePadding * 2) / 3; //解决右侧图片和内容对不齐问题
+ pxOneMaxWandH = MAX_WIDTH * 2 / 3;
+ initImageLayoutParams();
+ }
+
+ initView();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (MAX_WIDTH == 0) {
+ int width = measureWidth(widthMeasureSpec);
+ if (width > 0) {
+ MAX_WIDTH = width;
+ if (imagesList != null && imagesList.size() > 0) {
+ setList(imagesList);
+ }
+ }
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ /**
+ * Determines the width of this view
+ *
+ * @param measureSpec A measureSpec packed into an int
+ * @return The width of the view, honoring constraints from measureSpec
+ */
+ private int measureWidth(int measureSpec) {
+ int result = 0;
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+
+ if (specMode == MeasureSpec.EXACTLY) {
+ // We were told how big to be
+ result = specSize;
+ } else {
+ // Measure the text
+ // result = (int) mTextPaint.measureText(mText) + getPaddingLeft()
+ // + getPaddingRight();
+ if (specMode == MeasureSpec.AT_MOST) {
+ // Respect AT_MOST value if that was what is called for by
+ // measureSpec
+ result = Math.min(result, specSize);
+ }
+ }
+ return result;
+ }
+
+ private void initImageLayoutParams() {
+ int wrap = LayoutParams.WRAP_CONTENT;
+ int match = LayoutParams.MATCH_PARENT;
+
+ onePicPara = new LayoutParams(wrap, wrap);
+
+ moreParaColumnFirst = new LayoutParams(pxMoreWandH, pxMoreWandH);
+ morePara = new LayoutParams(pxMoreWandH, pxMoreWandH);
+ morePara.setMargins(pxImagePadding, 0, 0, 0);
+
+ rowPara = new LayoutParams(match, wrap);
+ }
+
+ // 根据imageView的数量初始化不同的View布局,还要为每一个View作点击效果
+ private void initView() {
+ this.setOrientation(VERTICAL);
+ this.removeAllViews();
+ if (MAX_WIDTH == 0) {
+ //为了触发onMeasure()来测量MultiImageView的最大宽度,MultiImageView的宽设置为match_parent
+ addView(new View(getContext()));
+ return;
+ }
+
+ if (imagesList == null || imagesList.size() == 0) {
+ return;
+ }
+
+ if (imagesList.size() == 1) {
+ addView(createImageView(0, false));
+ } else {
+ int allCount = imagesList.size();
+ if (allCount == 4) {
+ MAX_PER_ROW_COUNT = 2;
+ } else {
+ MAX_PER_ROW_COUNT = 3;
+ }
+ int rowCount = allCount / MAX_PER_ROW_COUNT + (allCount % MAX_PER_ROW_COUNT > 0 ? 1 : 0);// 行数
+ for (int rowCursor = 0; rowCursor < rowCount; rowCursor++) {
+ LinearLayout rowLayout = new LinearLayout(getContext());
+ rowLayout.setOrientation(LinearLayout.HORIZONTAL);
+
+ rowLayout.setLayoutParams(rowPara);
+ if (rowCursor != 0) {
+ rowLayout.setPadding(0, pxImagePadding, 0, 0);
+ }
+
+ int columnCount = allCount % MAX_PER_ROW_COUNT == 0 ? MAX_PER_ROW_COUNT : allCount %
+ MAX_PER_ROW_COUNT;//每行的列数
+ if (rowCursor != rowCount - 1) {
+ columnCount = MAX_PER_ROW_COUNT;
+ }
+ addView(rowLayout);
+
+ int rowOffset = rowCursor * MAX_PER_ROW_COUNT;// 行偏移
+ for (int columnCursor = 0; columnCursor < columnCount; columnCursor++) {
+ int position = columnCursor + rowOffset;
+ rowLayout.addView(createImageView(position, true));
+ }
+ }
+ }
+ }
+
+ private ImageView createImageView(int position, final boolean isMultiImage) {
+ String url = imagesList.get(position);
+ ImageView imageView = new ColorFilterImageView(getContext());
+ if (isMultiImage) {
+ imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ imageView.setLayoutParams(position % MAX_PER_ROW_COUNT == 0 ? moreParaColumnFirst : morePara);
+ } else {
+ imageView.setAdjustViewBounds(true);
+ imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ imageView.setMaxHeight(pxOneMaxWandH);
+ imageView.setLayoutParams(onePicPara);
+ }
+
+ imageView.setId(url.hashCode());
+ imageView.setOnClickListener(new ImageOnClickListener(position));
+ Glide.with(getContext())
+ .load(url)
+ .apply(GlideSetting.getGlideSetting()
+ .diskCacheStrategy(DiskCacheStrategy.ALL)
+ )
+
+ .into(imageView);
+ return imageView;
+ }
+
+ private class ImageOnClickListener implements View.OnClickListener {
+
+ private int position;
+
+ public ImageOnClickListener(int position) {
+ this.position = position;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (mOnItemClickListener != null) {
+ mOnItemClickListener.onItemClick(view, position);
+ }
+ }
+ }
+
+ public interface OnItemClickListener {
+ public void onItemClick(View view, int position);
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/popwindowlist/PopAdapter.java b/widget/src/main/java/com/hzecool/widget/popwindowlist/PopAdapter.java
new file mode 100644
index 0000000..d09bd92
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/popwindowlist/PopAdapter.java
@@ -0,0 +1,27 @@
+package com.hzecool.widget.popwindowlist;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.chad.library.adapter.base.BaseViewHolder;
+import com.hzecool.widget.R;
+
+import java.util.List;
+
+/**
+ * Created by tutu on 2017/4/17.
+ */
+
+public class PopAdapter extends BaseQuickAdapter {
+
+
+ public PopAdapter(int resId, List data) {
+
+ super(resId, data);
+
+
+ }
+
+ @Override
+ protected void convert(BaseViewHolder helper, String item) {
+ helper.setText(R.id.tv, item);
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/popwindowlist/RecyclerViewPop.java b/widget/src/main/java/com/hzecool/widget/popwindowlist/RecyclerViewPop.java
new file mode 100644
index 0000000..5259f3c
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/popwindowlist/RecyclerViewPop.java
@@ -0,0 +1,81 @@
+package com.hzecool.widget.popwindowlist;
+
+import android.content.Context;
+import android.graphics.drawable.BitmapDrawable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.hzecool.widget.R;
+import com.hzecool.widget.RecycleViewDivider;
+import com.hzecool.widget.utils.SizeUtils;
+
+import java.util.List;
+
+/**
+ * Created by tutu on 2017/4/17.
+ */
+
+public class RecyclerViewPop {
+
+ private PopupWindow popupWindow;
+ private List datas;
+ private View target;
+ private View content;
+ private RecyclerView recyclerView;
+ private PopAdapter popAdapter;
+ private Context context;
+ private int itemViewResId = -1;
+
+ public void setItemView(int resId) {
+ itemViewResId = resId;
+ }
+
+
+ public RecyclerViewPop(List datas, View target, Context context, BaseQuickAdapter.OnItemClickListener onItemClickListener) {
+ this.datas = datas;
+ this.target = target;
+ this.context = context;
+
+ content = LayoutInflater.from(context).inflate(R.layout.recyclerview_pop, null);
+
+ recyclerView = (RecyclerView) content.findViewById(R.id.rv);
+
+ if (itemViewResId == -1) {
+ popAdapter = new PopAdapter(R.layout.rv_pop_item, datas);
+ } else {
+ popAdapter = new PopAdapter(itemViewResId, datas);
+ }
+
+ RecycleViewDivider recycleViewDivider = new RecycleViewDivider(context, LinearLayout.HORIZONTAL, SizeUtils.dp2px(context,0.5),context.getResources().getColor(R.color.white));
+ recycleViewDivider.setMarginLeft(SizeUtils.dp2px(context,7));
+ recycleViewDivider.setMarginRight(SizeUtils.dp2px(context,7));
+ recyclerView.addItemDecoration(recycleViewDivider);
+ recyclerView.setLayoutManager(new LinearLayoutManager(context));
+ recyclerView.setAdapter(popAdapter);
+
+ popupWindow = new PopupWindow(content,
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
+
+ popupWindow.setBackgroundDrawable(new BitmapDrawable());
+
+ popAdapter.setOnItemClickListener(onItemClickListener);
+ }
+
+
+ public void show() {
+ popupWindow.showAsDropDown(target);
+ }
+
+ public void dissmiss() {
+ if (popupWindow != null && popupWindow.isShowing()) {
+ popupWindow.dismiss();
+ }
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/puzzle/AppUtil.java b/widget/src/main/java/com/hzecool/widget/puzzle/AppUtil.java
new file mode 100644
index 0000000..3460512
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/puzzle/AppUtil.java
@@ -0,0 +1,23 @@
+package com.hzecool.widget.puzzle;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+
+/**
+ * Created by dd on 16/1/13.
+ */
+public class AppUtil {
+
+ public static int getScreenWidth(Context context) {
+
+ DisplayMetrics dm = context.getResources().getDisplayMetrics();
+ return dm.widthPixels;
+ }
+
+ public static int getScreenHeight(Context context) {
+
+ DisplayMetrics dm = context.getResources().getDisplayMetrics();
+ return dm.heightPixels;
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/puzzle/Coordinates.java b/widget/src/main/java/com/hzecool/widget/puzzle/Coordinates.java
new file mode 100644
index 0000000..68fa118
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/puzzle/Coordinates.java
@@ -0,0 +1,32 @@
+package com.hzecool.widget.puzzle;
+
+/**
+ * Created by dd on 16/1/13.
+ * 坐标实体
+ */
+public class Coordinates {
+
+ private float x;
+ private float y;
+
+ public Coordinates(float x, float y){
+ this.x = x;
+ this.y = y;
+ }
+
+ public float getX() {
+ return x;
+ }
+
+ public void setX(float x) {
+ this.x = x;
+ }
+
+ public float getY() {
+ return y;
+ }
+
+ public void setY(float y) {
+ this.y = y;
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/puzzle/DensityUtil.java b/widget/src/main/java/com/hzecool/widget/puzzle/DensityUtil.java
new file mode 100644
index 0000000..f7128c0
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/puzzle/DensityUtil.java
@@ -0,0 +1,41 @@
+package com.hzecool.widget.puzzle;
+
+import android.content.Context;
+
+/**
+ * Created by dd on 16/1/13.
+ */
+public class DensityUtil {
+
+ /**
+ * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
+ */
+ public static int px2dip(Context context, float pxValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (pxValue / scale + 0.5f);
+ }
+
+ /**
+ * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
+ */
+ public static int dip2px(Context context, float dipValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (dipValue * scale + 0.5f);
+ }
+
+ /**
+ * 将px值转换为sp值,保证文字大小不变
+ */
+ public static int px2sp(Context context, float pxValue) {
+ final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+ return (int) (pxValue / fontScale + 0.5f);
+ }
+
+ /**
+ * 将sp值转换为px值,保证文字大小不变
+ */
+ public static int sp2px(Context context, float spValue) {
+ final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+ return (int) (spValue * fontScale + 0.5f);
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/puzzle/ImageBean.java b/widget/src/main/java/com/hzecool/widget/puzzle/ImageBean.java
new file mode 100644
index 0000000..f2ab1b4
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/puzzle/ImageBean.java
@@ -0,0 +1,186 @@
+package com.hzecool.widget.puzzle;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * Created by dd on 16/1/11.
+ */
+public class ImageBean implements Serializable {
+
+ private static final long serialVersionUID = 5700379542385619938L;
+
+ private long pictureId = -1;
+
+ private int id;
+ public String parentName;
+ public long size;
+ public String displayName;
+
+ public String path;
+ public boolean isChecked = false;
+ private String uploadTaskId;
+ private String pictureUrl;
+ private int photoYear;
+ private int photoMonth;
+ private int monthlyNum;
+
+ private Long photoId;
+ private Date photoTakenDate;
+ private String memo;
+ private Boolean monthlyCover;
+
+ private long photoDate;
+
+ public ImageBean() {
+ super();
+ }
+
+ public ImageBean(String parentName, long size, String displayName, String path, boolean isChecked) {
+ super();
+ this.parentName = parentName;
+ this.size = size;
+ this.displayName = displayName;
+ this.path = path;
+ this.isChecked = isChecked;
+ }
+
+ public long getPictureId() {
+ return pictureId;
+ }
+
+ public void setPictureId(long pictureId) {
+ this.pictureId = pictureId;
+ }
+
+ public Date getPhotoTakenDate() {
+ return photoTakenDate;
+ }
+
+ public void setPhotoTakenDate(Date photoTakenDate) {
+ this.photoTakenDate = photoTakenDate;
+ }
+
+
+ public long getPhotoDate() {
+ return photoDate;
+ }
+
+ public void setPhotoDate(long photoDate) {
+ this.photoDate = photoDate;
+ }
+
+ public String getPictureUrl() {
+ return pictureUrl;
+ }
+
+ public void setPictureUrl(String pictureUrl) {
+ this.pictureUrl = pictureUrl;
+ }
+
+ public int getPhotoYear() {
+ return photoYear;
+ }
+
+ public void setPhotoYear(int photoYear) {
+ this.photoYear = photoYear;
+ }
+
+ public int getPhotoMonth() {
+ return photoMonth;
+ }
+
+ public void setPhotoMonth(int photoMonth) {
+ this.photoMonth = photoMonth;
+ }
+
+ public int getMonthlyNum() {
+ return monthlyNum;
+ }
+
+ public void setMonthlyNum(int monthlyNum) {
+ this.monthlyNum = monthlyNum;
+ }
+
+ public Long getPhotoId() {
+ return photoId;
+ }
+
+ public void setPhotoId(Long photoId) {
+ this.photoId = photoId;
+ }
+
+
+ public String getMemo() {
+ return memo;
+ }
+
+ public void setMemo(String memo) {
+ this.memo = memo;
+ }
+
+ public Boolean getMonthlyCover() {
+ return monthlyCover;
+ }
+
+ public void setMonthlyCover(Boolean monthlyCover) {
+ this.monthlyCover = monthlyCover;
+ }
+
+
+ public String getUploadTaskId() {
+ return uploadTaskId;
+ }
+
+ public void setUploadTaskId(String uploadTaskId) {
+ this.uploadTaskId = uploadTaskId;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getParentName() {
+ return parentName;
+ }
+
+ public void setParentName(String parentName) {
+ this.parentName = parentName;
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ public void setSize(long size) {
+ this.size = size;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public boolean isChecked() {
+ return isChecked;
+ }
+
+ public void setChecked(boolean isChecked) {
+ this.isChecked = isChecked;
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/puzzle/ImageItem.java b/widget/src/main/java/com/hzecool/widget/puzzle/ImageItem.java
new file mode 100644
index 0000000..f1c2da5
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/puzzle/ImageItem.java
@@ -0,0 +1,20 @@
+package com.hzecool.widget.puzzle;
+
+import java.util.List;
+
+/**
+ * Created by dd on 16/1/13.
+ * 单张图片实体
+ */
+public class ImageItem {
+
+ private List coordinates;
+
+ public List getCoordinates() {
+ return coordinates;
+ }
+
+ public void setCoordinates(List coordinates) {
+ this.coordinates = coordinates;
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/puzzle/PuzzleView.java b/widget/src/main/java/com/hzecool/widget/puzzle/PuzzleView.java
new file mode 100644
index 0000000..d9304c0
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/puzzle/PuzzleView.java
@@ -0,0 +1,347 @@
+package com.hzecool.widget.puzzle;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by dd on 16/1/13.
+ * 拼图重要部分
+ */
+public class PuzzleView extends View {
+
+ private Context context;
+ private Path[] path;
+ private Bitmap[] bitmaps;
+ private boolean[] bitmapsFlag;
+ private float[][] pathLT;
+ private float[][] pathOffset;
+ private int pathNum;
+ private int viewWdh, viewHgt;
+ private int leftMargin;
+ private List pics;
+ private final static int MARGIN_HEIGHT = 100;
+ private List coordinateSetList;
+
+
+ public PuzzleView(Context context) {
+ super(context);
+ this.context = context;
+ }
+
+ public PuzzleView(Context context, AttributeSet attributeSet) {
+ super(context, attributeSet);
+ this.context = context;
+ initPath();
+ }
+
+ public PuzzleView(Context context, AttributeSet attributeSet, int defStyle) {
+ super(context, attributeSet, defStyle);
+ this.context = context;
+ initPath();
+ }
+
+ public void setPathCoordinate(List pathCoordinate) {
+ this.coordinateSetList = pathCoordinate;
+ initPath();
+ }
+
+ public void setPics(List imageBeans) {
+
+ leftMargin = (AppUtil.getScreenWidth(context) - dp2px(320)) / 2;
+ viewWdh = dp2px(320);
+ viewHgt = dp2px(450);
+ pics = new ArrayList<>();
+ if (imageBeans != null) {
+ pics.addAll(imageBeans);
+ }
+ pathNum = pics.size();
+ }
+
+ private void initPath() {
+ path = new Path[pathNum];
+ for (int i = 0; i < pathNum; i++) {
+ path[i] = new Path();
+ }
+ bitmapsFlag = new boolean[pathNum];
+
+ pathLT = new float[pathNum][2];
+ pathOffset = new float[pathNum][2];
+ for (int i = 0; i < pathNum; i++) {
+ bitmapsFlag[i] = false;
+ pathLT[i][0] = 0f;
+ pathLT[i][1] = 0f;
+ pathOffset[i][0] = 0f;
+ pathOffset[i][1] = 0f;
+ }
+
+ for (int i = 0; i < pathNum; i++) {
+ for (int j = 0; j < coordinateSetList.get(i).getCoordinates().size(); j++) {
+ float x = coordinateSetList.get(i).getCoordinates().get(j).getX();
+ float y = coordinateSetList.get(i).getCoordinates().get(j).getY();
+ if (j == 0) {
+ path[i].moveTo(dp2px(x), dp2px(y));
+ } else {
+ path[i].lineTo(dp2px(x), dp2px(y));
+ }
+ }
+ path[i].close();
+ }
+
+ // get bitmap
+ bitmaps = new Bitmap[pathNum];
+ for (int i = 0; i < pathNum; i++) {
+ BitmapFactory.Options opt = new BitmapFactory.Options();
+ opt.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(pics.get(i).getAbsolutePath(), opt);
+
+ int bmpWdh = opt.outWidth;
+ int bmpHgt = opt.outHeight;
+
+ Coordinates coordinate = caculateViewSize(coordinateSetList.get(i).getCoordinates());
+ int size = caculateSampleSize(bmpWdh, bmpHgt, dp2px(coordinate.getX()), dp2px(coordinate.getY()));
+ opt.inJustDecodeBounds = false;
+ opt.inSampleSize = size;
+
+ bitmaps[i] = scaleImage(BitmapFactory.decodeFile(pics.get(i).getAbsolutePath(), opt), dp2px(coordinate.getX()), dp2px(coordinate.getY()));
+ }
+ }
+
+ private Coordinates caculateViewSize(List list) {
+
+ float viewWidth;
+ float viewHeight;
+
+ viewWidth = caculateMaxCoordinateX(list) - caculateMinCoordinateX(list);
+ viewHeight = caculateMaxCoordinateY(list) - caculateMinCoordinateY(list);
+
+ return new Coordinates(viewWidth, viewHeight);
+ }
+
+
+ private int caculateSampleSize(int picWdh, int picHgt, int showWdh,
+ int showHgt) {
+ // 如果此时显示区域比图片大,直接返回
+ if ((showWdh < picWdh) && (showHgt < picHgt)) {
+ int wdhSample = picWdh / showWdh;
+ int hgtSample = picHgt / showHgt;
+ // 利用小的来处理
+ int sample = wdhSample > hgtSample ? hgtSample : wdhSample;
+ int minSample = 2;
+ while (sample > minSample) {
+ minSample *= 2;
+ }
+ return minSample >> 1;
+ } else {
+ return 0;
+ }
+ }
+
+ private float caculateMinCoordinateX(List list) {
+
+ float minX;
+ minX = list.get(0).getX();
+ for (int i = 1; i < list.size(); i++) {
+ if (list.get(i).getX() < minX) {
+ minX = list.get(i).getX();
+ }
+ }
+ return minX;
+ }
+
+ private float caculateMaxCoordinateX(List list) {
+
+ float maxX;
+ maxX = list.get(0).getX();
+ for (int i = 1; i < list.size(); i++) {
+ if (list.get(i).getX() > maxX) {
+ maxX = list.get(i).getX();
+ }
+ }
+ return maxX;
+ }
+
+ private float caculateMinCoordinateY(List list) {
+
+ float minY;
+ minY = list.get(0).getY();
+ for (int i = 1; i < list.size(); i++) {
+ if (list.get(i).getY() < minY) {
+ minY = list.get(i).getY();
+ }
+ }
+ return minY;
+ }
+
+ private float caculateMaxCoordinateY(List list) {
+
+ float maxY;
+ maxY = list.get(0).getY();
+ for (int i = 1; i < list.size(); i++) {
+ if (list.get(i).getY() > maxY) {
+ maxY = list.get(i).getY();
+ }
+ }
+ return maxY;
+ }
+
+ //图片缩放
+ private static Bitmap scaleImage(Bitmap bm, int newWidth, int newHeight) {
+ if (bm == null) {
+ return null;
+ }
+ int width = bm.getWidth();
+ int height = bm.getHeight();
+ float scaleWidth = ((float) newWidth) / width;
+ float scaleHeight = ((float) newHeight) / height;
+ float scale = 1;
+ if (scaleWidth >= scaleHeight) {
+ scale = scaleWidth;
+ } else {
+ scale = scaleHeight;
+ }
+ Matrix matrix = new Matrix();
+ matrix.postScale(scale, scale);
+ Bitmap newbm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix,
+ true);
+ return newbm;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // TODO Auto-generated method stub
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ setMeasuredDimension(viewWdh, viewHgt);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawColor(Color.TRANSPARENT);// 显示背景颜色
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setColor(Color.WHITE);
+// canvas.drawPaint(paint);
+ // draw1(canvas);
+ startDraw(canvas, paint);
+ }
+
+
+ private void startDraw(Canvas canvas, Paint paint) {
+ for (int i = 0; i < pathNum; i++) {
+ canvas.save();
+ drawScene(canvas, paint, i);
+ canvas.restore();
+ }
+ }
+
+ private void drawScene(Canvas canvas, Paint paint, int idx) {
+ canvas.clipPath(path[idx]);
+ canvas.drawColor(Color.GRAY);
+ if (bitmapsFlag[idx]) {
+ canvas.drawBitmap(bitmaps[idx], dp2px(caculateMinCoordinateX(coordinateSetList.get(idx).getCoordinates())) + pathOffsetX + pathOffset[idx][0],
+ dp2px(caculateMinCoordinateY(coordinateSetList.get(idx).getCoordinates())) + pathOffsetY + pathOffset[idx][1], paint);
+ } else {
+ canvas.drawBitmap(bitmaps[idx], dp2px(caculateMinCoordinateX(coordinateSetList.get(idx).getCoordinates())) + pathOffset[idx][0],
+ dp2px(caculateMinCoordinateY(coordinateSetList.get(idx).getCoordinates())) + pathOffset[idx][1], paint);
+ }
+ }
+
+ private int dp2px(float point) {
+ return DensityUtil.dip2px(getContext(), point);
+ }
+
+ float ptx, pty;
+ float pathOffsetX, pathOffsetY;
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ for (int i = 0; i < pathNum; i++) {
+ bitmapsFlag[i] = false;
+ }
+ ptx = event.getRawX() - dp2px(leftMargin);
+ pty = event.getRawY() - dp2px(MARGIN_HEIGHT);
+ pathOffsetX = 0;
+ pathOffsetY = 0;
+ int cflag = 0;
+ for (cflag = 0; cflag < pathNum; cflag++) {
+ if (contains(path[cflag], ptx, pty)) {
+ bitmapsFlag[cflag] = true;
+ break;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ pathOffsetX = event.getRawX() - dp2px(leftMargin) - ptx;
+ pathOffsetY = event.getRawY() - dp2px(MARGIN_HEIGHT) - pty;
+ invalidate();
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ break;
+ case MotionEvent.ACTION_UP:
+ for (int i = 0; i < pathNum; i++) {
+ if (bitmapsFlag[i]) {
+ pathOffset[i][0] += event.getRawX() - dp2px(leftMargin) - ptx;
+ pathOffset[i][1] += event.getRawY() - dp2px(MARGIN_HEIGHT) - pty;
+
+ if (pathOffset[i][0] > 0) {
+ pathOffset[i][0] = 0;
+ }
+ if (pathOffset[i][0] < -(bitmaps[i].getWidth() - getViewWidth(coordinateSetList.get(i).getCoordinates()))) {
+ pathOffset[i][0] = -(bitmaps[i].getWidth() - getViewWidth(coordinateSetList.get(i).getCoordinates()));
+ }
+ if (pathOffset[i][1] > 0) {
+ pathOffset[i][1] = 0;
+ }
+ if (pathOffset[i][1] < -(bitmaps[i].getHeight() - getViewHeight(coordinateSetList.get(i).getCoordinates()))) {
+ pathOffset[i][1] = -(bitmaps[i].getHeight() - getViewHeight(coordinateSetList.get(i).getCoordinates()));
+ }
+ bitmapsFlag[i] = false;
+ break;
+ }
+ }
+ invalidate();
+ break;
+ default:
+ break;
+ }
+
+ return true;
+ }
+
+ private boolean contains(Path parapath, float pointx, float pointy) {
+ RectF localRectF = new RectF();
+ parapath.computeBounds(localRectF, true);
+ Region localRegion = new Region();
+ localRegion.setPath(parapath, new Region((int) localRectF.left,
+ (int) localRectF.top, (int) localRectF.right,
+ (int) localRectF.bottom));
+ return localRegion.contains((int) pointx, (int) pointy);
+ }
+
+ private float getViewWidth(List list) {
+
+ return dp2px(caculateMaxCoordinateX(list) - caculateMinCoordinateX(list));
+ }
+
+ private float getViewHeight(List list) {
+
+ return dp2px(caculateMaxCoordinateY(list) - caculateMinCoordinateY(list));
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/puzzle/SelectItem.java b/widget/src/main/java/com/hzecool/widget/puzzle/SelectItem.java
new file mode 100644
index 0000000..16477cf
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/puzzle/SelectItem.java
@@ -0,0 +1,29 @@
+package com.hzecool.widget.puzzle;
+
+import android.content.Context;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+/**
+ * Created by dd on 16/1/13.
+ */
+public class SelectItem extends RelativeLayout {
+
+
+ private ImageView mImageView;
+
+ public SelectItem(Context context) {
+ super(context);
+ mImageView = new ImageView(context);
+ mImageView.setAdjustViewBounds(true);
+ mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ LayoutParams mImageViewRLP = new LayoutParams(DensityUtil.dip2px(context, 60), DensityUtil.dip2px(context, 60));
+ mImageViewRLP.addRule(RelativeLayout.CENTER_IN_PARENT);
+ mImageViewRLP.setMargins(0, DensityUtil.dip2px(context, 30), DensityUtil.dip2px(context, 20), 0);
+ addView(mImageView, mImageViewRLP);
+ }
+
+ public ImageView getmImageView() {
+ return mImageView;
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/riseedittext/RiseEditText.java b/widget/src/main/java/com/hzecool/widget/riseedittext/RiseEditText.java
new file mode 100644
index 0000000..19d907a
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/riseedittext/RiseEditText.java
@@ -0,0 +1,161 @@
+package com.hzecool.widget.riseedittext;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.text.InputType;
+import android.util.AttributeSet;
+import android.view.ActionMode;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import com.hzecool.widget.R;
+import com.hzecool.widget.utils.SizeUtils;
+
+/**
+ * 可以随着传入的字符串的长度增加或减少显示的方框形状的edittext,讲字符串分开显示
+ * Created by song on 2017/6/19.
+ */
+
+public class RiseEditText extends LinearLayout {
+
+ private LinearLayout mLl_editext;
+
+ public RiseEditText(Context context) {
+ this(context, null);
+ }
+
+ public RiseEditText(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RiseEditText(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ View.inflate(getContext(), R.layout.edittext_layout, this);
+ mLl_editext = (LinearLayout) findViewById(R.id.ll_edittext);
+ }
+
+ private String content;
+
+ public void setContent(String content) {
+ this.content = content;
+ //初始化edittext的数量
+ initEditextNum();
+ }
+
+ private void initEditextNum() {
+ mLl_editext.removeAllViews();
+ char[] chars = content.toCharArray();
+ for (int i = 0; i < content.length(); i++) {
+ EditText editText = new EditText(getContext());
+ // MaterialEditText materialEditText = new MaterialEditText(getContext());
+ editText.setBackgroundResource(R.drawable.bg_square_voice);
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ editText.setPadding(SizeUtils.dp2px(getContext(), 10), 0, SizeUtils.dp2px(getContext(), 10), 0);
+ params.topMargin = SizeUtils.dp2px(getContext(), 4);
+ params.bottomMargin = SizeUtils.dp2px(getContext(), 4);
+ params.leftMargin = SizeUtils.dp2px(getContext(), 10);
+ editText.setTextSize(SizeUtils.sp2px(getContext(), 8));
+ //限制输入内容的长度
+ editText.setGravity(Gravity.CENTER);
+ editText.setText(String.valueOf(chars[i]));
+ //去除滑动内容弹出复制粘贴
+ editText.setCustomSelectionActionModeCallback(mCallback);
+ //去除长按复制功能
+ editText.setLongClickable(false);
+ //默认不全部选中内容
+ editText.setSelectAllOnFocus(false);
+ //清除焦点
+ editText.clearFocus();
+ //默认不选中内容
+ editText.setSelected(false);
+ //不显示光标
+ editText.setCursorVisible(false);
+ //添加view
+ mLl_editext.addView(editText, params);
+ editText.setOnClickListener(mPointClickListener);
+ /*editText.setOnFocusChangeListener(new OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ ((EditText) v).setText(null);
+ }
+ });*/
+ }
+ }
+
+
+ /**
+ * 限制输入类型,1仅输入数字 0任意字符 默认是任意字符.
+ *
+ * @param type
+ */
+ public void setInputType(int type) {
+ int childCount = mLl_editext.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ EditText editText = (EditText) mLl_editext.getChildAt(i);
+ if (type == 0) {
+ //输入任意字符
+ editText.setInputType(InputType.TYPE_CLASS_TEXT);
+ } else if (type == 1) {
+ //仅仅输入数字
+ editText.setInputType(InputType.TYPE_CLASS_NUMBER);
+ }
+ }
+ }
+
+ /**
+ * 去掉复制粘贴的功能
+ */
+ private ActionMode.Callback mCallback = new ActionMode.Callback() {
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ return false;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+
+ }
+ };
+
+ private OnClickListener mPointClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ((EditText) v).setText(null);
+ }
+ };
+
+ /**
+ * 实现editText的getText()功能
+ *
+ * @return
+ */
+ public String getText() {
+ String str = "";
+ int count = mLl_editext.getChildCount();
+ for (int i = 0; i < count; i++) {
+ EditText child = (EditText) mLl_editext.getChildAt(i);
+ str = str + child.getText();
+ }
+ return str;
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/riseedittext/RiseEditTextNum.java b/widget/src/main/java/com/hzecool/widget/riseedittext/RiseEditTextNum.java
new file mode 100644
index 0000000..f62c839
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/riseedittext/RiseEditTextNum.java
@@ -0,0 +1,144 @@
+package com.hzecool.widget.riseedittext;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.text.InputType;
+import android.util.AttributeSet;
+import android.view.ActionMode;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import com.hzecool.widget.R;
+import com.hzecool.widget.utils.SizeUtils;
+
+/**
+ * 仅支持输入数字的自增的edittext
+ * Created by song on 2017/7/3.
+ */
+
+public class RiseEditTextNum extends LinearLayout {
+ private LinearLayout mLl_editext;
+
+ public RiseEditTextNum(Context context) {
+ this(context,null);
+ }
+
+ public RiseEditTextNum(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs,0);
+ }
+
+ public RiseEditTextNum(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ View.inflate(getContext(), R.layout.edittext_layout, this);
+ mLl_editext = (LinearLayout) findViewById(R.id.ll_edittext);
+ }
+
+ private String content;
+
+ public void setContent(String content) {
+ this.content = content;
+ //初始化edittext的数量
+ initEditextNum();
+ }
+
+ private void initEditextNum() {
+ mLl_editext.removeAllViews();
+ char[] chars = content.toCharArray();
+ for (int i = 0; i < content.length(); i++) {
+ EditText editText = new EditText(getContext());
+ // MaterialEditText materialEditText = new MaterialEditText(getContext());
+ editText.setBackgroundResource(R.drawable.bg_square_voice);
+ LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ editText.setPadding(SizeUtils.dp2px(getContext(), 10), 0, SizeUtils.dp2px(getContext(), 10), 0);
+ params.topMargin = SizeUtils.dp2px(getContext(), 4);
+ params.bottomMargin = SizeUtils.dp2px(getContext(), 4);
+ params.leftMargin = SizeUtils.dp2px(getContext(), 10);
+ editText.setTextSize(SizeUtils.sp2px(getContext(), 8));
+ editText.setInputType(InputType.TYPE_CLASS_NUMBER);
+ //限制输入内容的长度
+ // editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(1)});
+ editText.setGravity(Gravity.CENTER);
+ editText.setText(String.valueOf(chars[i]));
+ //去除滑动内容弹出复制粘贴
+ editText.setCustomSelectionActionModeCallback(mCallback);
+ //去除长按复制功能
+ editText.setLongClickable(false);
+ //默认不全部选中内容
+ editText.setSelectAllOnFocus(false);
+ //清除焦点
+ editText.clearFocus();
+ //默认不选中内容
+ editText.setSelected(false);
+ //不显示光标
+ editText.setCursorVisible(false);
+ //添加view
+ mLl_editext.addView(editText, params);
+ editText.setOnClickListener(mPointClickListener);
+ /*editText.setOnFocusChangeListener(new OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ ((EditText) v).setText(null);
+ }
+ });*/
+ }
+ }
+
+
+
+ /**
+ * 去掉复制粘贴的功能
+ */
+ private ActionMode.Callback mCallback = new ActionMode.Callback() {
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ return false;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+
+ }
+ };
+
+ private OnClickListener mPointClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ((EditText) v).setText(null);
+ }
+ };
+
+ /**
+ * 实现editText的getText()功能
+ *
+ * @return
+ */
+ public String getText() {
+ String str = "";
+ int count = mLl_editext.getChildCount();
+ for (int i = 0; i < count; i++) {
+ EditText child = (EditText) mLl_editext.getChildAt(i);
+ str = str + child.getText();
+ }
+ return str;
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/risenumber/IRiseNumber.java b/widget/src/main/java/com/hzecool/widget/risenumber/IRiseNumber.java
new file mode 100644
index 0000000..d9bc4fd
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/risenumber/IRiseNumber.java
@@ -0,0 +1,43 @@
+package com.hzecool.widget.risenumber;
+
+/**
+ * Created by song on 2017/5/16.
+ */
+
+public interface IRiseNumber {
+ /**
+ * 开始播放动画的方法
+ */
+ public void start();
+
+ /**
+ * 设置小数
+ *
+ * @param number
+ * @return
+ */
+ public void withNumber(float number);
+
+ /**
+ * 设置整数
+ *
+ * @param number
+ * @return
+ */
+ public void withNumber(int number);
+
+ /**
+ * 设置动画播放时长
+ *
+ * @param duration
+ * @return
+ */
+ public void setDuration(long duration);
+
+ /**
+ * 设置动画结束监听器
+ *
+ * @param callback
+ */
+ public void setOnEndListener(RiseNumberTextView.EndListener callback);
+}
diff --git a/widget/src/main/java/com/hzecool/widget/risenumber/RiseNumberTextView.java b/widget/src/main/java/com/hzecool/widget/risenumber/RiseNumberTextView.java
new file mode 100644
index 0000000..3d88587
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/risenumber/RiseNumberTextView.java
@@ -0,0 +1,219 @@
+package com.hzecool.widget.risenumber;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Color;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import java.text.DecimalFormat;
+
+/**
+ * Created by song on 2017/5/16.
+ */
+
+public class RiseNumberTextView extends TextView implements IRiseNumber{
+ private static final int STOPPED = 0;
+
+ private static final int RUNNING = 1;
+
+ private int mPlayingState = STOPPED;
+
+ private float number;
+
+ private float fromNumber;
+
+ /**
+ * 动画播放时长
+ */
+ private long duration = 1500;
+ /**
+ * 1.int 2.float
+ */
+ private int numberType = 2;
+
+ private DecimalFormat fnum;
+
+ private EndListener mEndListener = null;
+
+ final static int[] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
+ 99999999, 999999999, Integer.MAX_VALUE };
+
+ /**
+ * 构造方法
+ *
+ * @param context
+ */
+ public RiseNumberTextView(Context context) {
+ super(context);
+ }
+
+ /**
+ * 使用xml布局文件默认的被调用的构造方法
+ *
+ * @param context
+ * @param attr
+ */
+ public RiseNumberTextView(Context context, AttributeSet attr) {
+ super(context, attr);
+ setTextColor(Color.RED);
+ setTextSize(30);
+ }
+
+ public RiseNumberTextView(Context context, AttributeSet attr, int defStyle) {
+ super(context, attr, defStyle);
+ }
+
+ /**
+ * 判断动画是否正在播放
+ *
+ * @return
+ */
+ public boolean isRunning() {
+ return (mPlayingState == RUNNING);
+ }
+
+ /**
+ * 跑小数动画
+ */
+ private void runFloat() {
+ ValueAnimator valueAnimator = ValueAnimator.ofFloat(fromNumber, number);
+ valueAnimator.setDuration(duration);
+
+ valueAnimator
+ .addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+
+ setText(fnum.format(Float.parseFloat(valueAnimator
+ .getAnimatedValue().toString())));
+ if (valueAnimator.getAnimatedFraction() >= 1) {
+ mPlayingState = STOPPED;
+ if (mEndListener != null)
+ mEndListener.onEndFinish();
+ }
+ }
+
+
+ });
+
+ valueAnimator.start();
+ }
+
+ /**
+ * 跑整数动画
+ */
+ private void runInt() {
+
+ ValueAnimator valueAnimator = ValueAnimator.ofInt((int) fromNumber,
+ (int) number);
+ valueAnimator.setDuration(duration);
+
+ valueAnimator
+ .addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ //设置瞬时的数据值到界面上
+ setText(valueAnimator.getAnimatedValue().toString());
+ if (valueAnimator.getAnimatedFraction() >= 1) {
+ //设置状态为停止
+ mPlayingState = STOPPED;
+ if (mEndListener != null)
+ //通知监听器,动画结束事件
+ mEndListener.onEndFinish();
+ }
+ }
+ });
+ valueAnimator.start();
+ }
+
+ static int sizeOfInt(int x) {
+ for (int i = 0;; i++){
+ if (x <= sizeTable[i])
+ return i + 1;
+ }
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ fnum = new DecimalFormat("##0.00");
+ }
+
+ /**
+ * 开始播放动画
+ */
+ @Override
+ public void start() {
+
+ if (!isRunning()) {
+ mPlayingState = RUNNING;
+ if (numberType == 1)
+ runInt();
+ else
+ runFloat();
+ }
+ }
+
+ /**
+ * 设置一个小数进来
+ */
+ @Override
+ public void withNumber(float number) {
+
+ this.number = number;
+ numberType = 2;
+ /*if (number > 1000) {
+ fromNumber = number
+ - (float) Math.pow(10, sizeOfInt((int) number) - 1);
+ } else {
+ fromNumber = number / 2;
+ }*/
+ fromNumber = number / 2;
+
+ }
+
+ /**
+ * 设置一个整数进来
+ */
+ @Override
+ public void withNumber(int number) {
+ this.number = number;
+ numberType = 1;
+ /* if (number > 1000) {
+ fromNumber = number
+ - (float) Math.pow(10, sizeOfInt((int) number) - 2);
+ } else {
+ fromNumber = number / 2;
+ }*/
+ fromNumber = number / 2;
+ }
+
+ /**
+ * 设置动画播放时间
+ */
+ @Override
+ public void setDuration(long duration) {
+ this.duration = duration;
+ }
+
+ /**
+ * 设置动画结束监听器
+ */
+ @Override
+ public void setOnEndListener(EndListener callback) {
+ mEndListener = callback;
+ }
+
+ /**
+ * 定义动画结束接口
+ *
+ *
+ */
+ public interface EndListener {
+ /**
+ * 当动画播放结束时的回调方法
+ */
+ public void onEndFinish();
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/svprogresshud/SVProgressHUD.java b/widget/src/main/java/com/hzecool/widget/svprogresshud/SVProgressHUD.java
new file mode 100644
index 0000000..c3a0b0b
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/svprogresshud/SVProgressHUD.java
@@ -0,0 +1,348 @@
+package com.hzecool.widget.svprogresshud;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.FrameLayout;
+
+import com.hzecool.widget.R;
+import com.hzecool.widget.svprogresshud.listener.OnDismissListener;
+import com.hzecool.widget.svprogresshud.view.SVCircleProgressBar;
+import com.hzecool.widget.svprogresshud.view.SVProgressDefaultView;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Created by Sai on 15/8/15.
+ */
+public class SVProgressHUD {
+ private WeakReference contextWeak;
+ private static final long DISMISSDELAYED = 1000;
+ private SVProgressHUDMaskType mSVProgressHUDMaskType;
+ private boolean isShowing;
+ private boolean isDismissing;
+
+ public enum SVProgressHUDMaskType {
+ None, // 允许遮罩下面控件点击
+ Clear, // 不允许遮罩下面控件点击
+ Black, // 不允许遮罩下面控件点击,背景黑色半透明
+ Gradient, // 不允许遮罩下面控件点击,背景渐变半透明
+ ClearCancel, // 不允许遮罩下面控件点击,点击遮罩消失
+ BlackCancel, // 不允许遮罩下面控件点击,背景黑色半透明,点击遮罩消失
+ GradientCancel // 不允许遮罩下面控件点击,背景渐变半透明,点击遮罩消失
+ ;
+ }
+
+ private final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM
+ );
+ private ViewGroup decorView;//activity的根View
+ private ViewGroup rootView;// mSharedView 的 根View
+ private SVProgressDefaultView mSharedView;
+
+ private Animation outAnim;
+ private Animation inAnim;
+ private int gravity = Gravity.CENTER;
+ private OnDismissListener onDismissListener;
+
+
+ public SVProgressHUD(Context context){
+ this.contextWeak = new WeakReference<>(context);
+ gravity = Gravity.CENTER;
+ initViews();
+ initDefaultView();
+ initAnimation();
+ }
+
+ protected void initViews() {
+ Context context = contextWeak.get();
+ if(context == null) return;
+
+ LayoutInflater layoutInflater = LayoutInflater.from(context);
+ decorView = (ViewGroup) ((Activity) context).getWindow().getDecorView().findViewById(android.R.id.content);
+ rootView = (ViewGroup) layoutInflater.inflate(R.layout.layout_svprogresshud, null, false);
+ rootView.setLayoutParams(new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
+ ));
+ }
+ protected void initDefaultView(){
+ Context context = contextWeak.get();
+ if(context == null) return;
+
+ mSharedView = new SVProgressDefaultView(context);
+ params.gravity = gravity;
+ mSharedView.setLayoutParams(params);
+ }
+
+ protected void initAnimation() {
+ if(inAnim == null)
+ inAnim = getInAnimation();
+ if(outAnim == null)
+ outAnim = getOutAnimation();
+ }
+
+ /**
+ * show的时候调用
+ */
+ private void onAttached() {
+ isShowing = true;
+ decorView.addView(rootView);
+ if(mSharedView.getParent()!=null)((ViewGroup)mSharedView.getParent()).removeView(mSharedView);
+ rootView.addView(mSharedView);
+ }
+
+ /**
+ * 添加这个View到Activity的根视图
+ */
+ private void svShow() {
+
+ mHandler.removeCallbacksAndMessages(null);
+ onAttached();
+
+ mSharedView.startAnimation(inAnim);
+
+ }
+
+ public void show() {
+ if(isShowing())return;
+ setMaskType(SVProgressHUDMaskType.Black);
+ mSharedView.show();
+ svShow();
+ }
+
+ public void showWithMaskType(SVProgressHUDMaskType maskType) {
+ if(isShowing())return;
+ //判断maskType
+ setMaskType(maskType);
+ mSharedView.show();
+ svShow();
+ }
+
+ public void showWithStatus(String string) {
+ if(isShowing())return;
+ setMaskType(SVProgressHUDMaskType.Black);
+ mSharedView.showWithStatus(string);
+ svShow();
+ }
+
+ public void showWithStatus(String string, SVProgressHUDMaskType maskType) {
+ if(isShowing())return;
+ setMaskType(maskType);
+ mSharedView.showWithStatus(string);
+ svShow();
+ }
+
+ public void showInfoWithStatus(String string) {
+ if(isShowing())return;
+ setMaskType(SVProgressHUDMaskType.Black);
+ mSharedView.showInfoWithStatus(string);
+ svShow();
+ scheduleDismiss();
+ }
+
+ public void showInfoWithStatus(String string, SVProgressHUDMaskType maskType) {
+ if(isShowing())return;
+ setMaskType(maskType);
+ mSharedView.showInfoWithStatus(string);
+ svShow();
+ scheduleDismiss();
+ }
+
+ public void showSuccessWithStatus(String string) {
+ if(isShowing())return;
+ setMaskType(SVProgressHUDMaskType.Black);
+ mSharedView.showSuccessWithStatus(string);
+ svShow();
+ scheduleDismiss();
+ }
+
+ public void showSuccessWithStatus(String string, SVProgressHUDMaskType maskType) {
+ if(isShowing())return;
+ setMaskType(maskType);
+ mSharedView.showSuccessWithStatus(string);
+ svShow();
+ scheduleDismiss();
+ }
+
+ public void showErrorWithStatus(String string) {
+ if(isShowing())return;
+ setMaskType(SVProgressHUDMaskType.Black);
+ mSharedView.showErrorWithStatus(string);
+ svShow();
+ scheduleDismiss();
+ }
+
+ public void showErrorWithStatus(String string, SVProgressHUDMaskType maskType) {
+ if(isShowing())return;
+ setMaskType(maskType);
+ mSharedView.showErrorWithStatus(string);
+ svShow();
+ scheduleDismiss();
+ }
+
+ public void showWithProgress(String string, SVProgressHUDMaskType maskType) {
+ if(isShowing())return;
+ setMaskType(maskType);
+ mSharedView.showWithProgress(string);
+ svShow();
+ }
+
+ public SVCircleProgressBar getProgressBar(){
+ return mSharedView.getCircleProgressBar();
+ }
+ public void setText(String string){
+ mSharedView.setText(string);
+ }
+
+ private void setMaskType(SVProgressHUDMaskType maskType) {
+ mSVProgressHUDMaskType = maskType;
+ switch (mSVProgressHUDMaskType) {
+ case None:
+ configMaskType(android.R.color.transparent, false, false);
+ break;
+ case Clear:
+ configMaskType(android.R.color.transparent, true, false);
+ break;
+ case ClearCancel:
+ configMaskType(android.R.color.transparent, true, true);
+ break;
+ case Black:
+ configMaskType(R.color.bgColor_overlay, true, false);
+ break;
+ case BlackCancel:
+ configMaskType(R.color.bgColor_overlay, true, true);
+ break;
+ case Gradient:
+ configMaskType(R.drawable.bg_overlay_gradient, true, false);
+ break;
+ case GradientCancel:
+ configMaskType(R.drawable.bg_overlay_gradient, true, true);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void configMaskType(int bg, boolean clickable, boolean cancelable) {
+ rootView.setBackgroundResource(bg);
+ rootView.setClickable(clickable);
+ setCancelable(cancelable);
+ }
+
+ /**
+ * 检测该View是不是已经添加到根视图
+ *
+ * @return 如果视图已经存在该View返回true
+ */
+ public boolean isShowing() {
+ return rootView.getParent() != null || isShowing;
+ }
+
+ public void dismiss() {
+ if(isDismissing)return;
+ isDismissing = true;
+ //消失动画
+ outAnim.setAnimationListener(outAnimListener);
+ mSharedView.dismiss();
+ mSharedView.startAnimation(outAnim);
+ }
+
+ public void dismissImmediately() {
+ mSharedView.dismiss();
+ rootView.removeView(mSharedView);
+ decorView.removeView(rootView);
+ isShowing = false;
+ isDismissing = false;
+ if(onDismissListener != null){
+ onDismissListener.onDismiss(this);
+ }
+
+ }
+
+ public Animation getInAnimation() {
+ Context context = contextWeak.get();
+ if(context == null) return null;
+
+ int res = SVProgressHUDAnimateUtil.getAnimationResource(this.gravity, true);
+ return AnimationUtils.loadAnimation(context, res);
+ }
+
+ public Animation getOutAnimation() {
+ Context context = contextWeak.get();
+ if(context == null) return null;
+
+ int res = SVProgressHUDAnimateUtil.getAnimationResource(this.gravity, false);
+ return AnimationUtils.loadAnimation(context, res);
+ }
+
+ private void setCancelable(boolean isCancelable) {
+ View view = rootView.findViewById(R.id.sv_outmost_container);
+
+ if (isCancelable) {
+ view.setOnTouchListener(onCancelableTouchListener);
+ } else {
+ view.setOnTouchListener(null);
+ }
+ }
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+ dismiss();
+ }
+ };
+
+ private void scheduleDismiss() {
+ mHandler.removeCallbacksAndMessages(null);
+ mHandler.sendEmptyMessageDelayed(0, DISMISSDELAYED);
+ }
+
+ /**
+ * Called when the user touch on black overlay in order to dismiss the dialog
+ */
+ private final View.OnTouchListener onCancelableTouchListener = new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ dismiss();
+ setCancelable(false);
+ }
+ return false;
+ }
+ };
+
+ private Animation.AnimationListener outAnimListener = new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ dismissImmediately();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+
+ }
+ };
+
+ public void setOnDismissListener(OnDismissListener listener){
+ this.onDismissListener = listener;
+ }
+
+ public OnDismissListener getOnDismissListener(){
+ return onDismissListener;
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/svprogresshud/SVProgressHUDAnimateUtil.java b/widget/src/main/java/com/hzecool/widget/svprogresshud/SVProgressHUDAnimateUtil.java
new file mode 100644
index 0000000..734acf3
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/svprogresshud/SVProgressHUDAnimateUtil.java
@@ -0,0 +1,25 @@
+package com.hzecool.widget.svprogresshud;
+
+import android.view.Gravity;
+
+import com.hzecool.widget.R;
+
+/**
+ * Created by Sai on 15/8/16.
+ */
+public class SVProgressHUDAnimateUtil {
+ private static final int INVALID = -1;
+ static int getAnimationResource(int gravity, boolean isInAnimation) {
+ switch (gravity) {
+ case Gravity.TOP:
+ return isInAnimation ? R.anim.svslide_in_top : R.anim.svslide_out_top;
+ case Gravity.BOTTOM:
+ return isInAnimation ? R.anim.svslide_in_bottom : R.anim.svslide_out_bottom;
+ case Gravity.CENTER:
+ return isInAnimation ? R.anim.svfade_in_center : R.anim.svfade_out_center;
+ default:
+ // This case is not implemented because we don't expect any other gravity at the moment
+ }
+ return INVALID;
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/svprogresshud/listener/OnDismissListener.java b/widget/src/main/java/com/hzecool/widget/svprogresshud/listener/OnDismissListener.java
new file mode 100644
index 0000000..50f2530
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/svprogresshud/listener/OnDismissListener.java
@@ -0,0 +1,10 @@
+package com.hzecool.widget.svprogresshud.listener;
+
+import com.hzecool.widget.svprogresshud.SVProgressHUD;
+
+/**
+ * Created by Sai on 16/7/31.
+ */
+public interface OnDismissListener {
+ void onDismiss(SVProgressHUD hud);
+}
diff --git a/widget/src/main/java/com/hzecool/widget/svprogresshud/view/SVCircleProgressBar.java b/widget/src/main/java/com/hzecool/widget/svprogresshud/view/SVCircleProgressBar.java
new file mode 100644
index 0000000..c651c69
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/svprogresshud/view/SVCircleProgressBar.java
@@ -0,0 +1,195 @@
+package com.hzecool.widget.svprogresshud.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.hzecool.widget.R;
+
+
+/**
+ * Created by Sai on 15/9/1.
+ */
+public class SVCircleProgressBar extends View {
+ /**
+ * 画笔对象的引用
+ */
+ private Paint paint;
+
+ /**
+ * 圆环的颜色
+ */
+ private int roundColor;
+
+ /**
+ * 圆环进度的颜色
+ */
+ private int roundProgressColor;
+
+ /**
+ * 圆环的宽度
+ */
+ private float roundWidth;
+
+ /**
+ * 最大进度
+ */
+ private int max;
+
+ /**
+ * 当前进度
+ */
+ private int progress;
+
+ /**
+ * 进度的风格,实心或者空心
+ */
+ private int style;
+
+ public static final int STROKE = 0;
+ public static final int FILL = 1;
+
+ public SVCircleProgressBar(Context context) {
+ this(context, null);
+ }
+
+ public SVCircleProgressBar(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SVCircleProgressBar(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ paint = new Paint();
+
+ TypedArray mTypedArray = context.obtainStyledAttributes(attrs,
+ R.styleable.SVCircleProgressBar);
+
+ // 获取自定义属性和默认值
+ roundColor = mTypedArray.getColor(R.styleable.SVCircleProgressBar_svprogress_roundColor, Color.BLUE);
+ roundProgressColor = mTypedArray.getColor(R.styleable.SVCircleProgressBar_svprogress_roundProgressColor,
+ Color.GRAY);
+ roundWidth = mTypedArray.getDimension(R.styleable.SVCircleProgressBar_svprogress_roundWidth, 5);
+ max = mTypedArray.getInteger(R.styleable.SVCircleProgressBar_svprogress_max, 100);
+ style = mTypedArray.getInt(R.styleable.SVCircleProgressBar_svprogress_style, 0);
+
+ mTypedArray.recycle();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ /**
+ * 画最外层的大圆环
+ */
+ int centre = getWidth() / 2; // 获取圆心的x坐标
+ int radius = (int) (centre - roundWidth / 2); // 圆环的半径
+ paint.setAntiAlias(true); // 消除锯齿
+ paint.setColor(roundColor); // 设置圆环的颜色
+ paint.setStyle(Paint.Style.STROKE); // 设置空心
+ paint.setStrokeWidth(roundWidth); // 设置圆环的宽度
+ canvas.drawCircle(centre, centre, radius, paint); // 画出圆环
+
+
+ /**
+ * 画圆弧 ,画圆环的进度
+ */
+
+ // 设置进度是实心还是空心
+ paint.setStrokeWidth(roundWidth); // 设置圆环的宽度
+ paint.setColor(roundProgressColor); // 设置进度的颜色
+ RectF oval = new RectF(centre - radius, centre - radius, centre
+ + radius, centre + radius); // 用于定义的圆弧的形状和大小的界限
+
+ switch (style) {
+ case STROKE: {
+ paint.setStyle(Paint.Style.STROKE);
+ canvas.drawArc(oval, 270, 360 * progress / max, false, paint); // 根据进度画圆弧
+ break;
+ }
+ case FILL: {
+ paint.setStyle(Paint.Style.FILL_AND_STROKE);
+ if (progress != 0)
+ canvas.drawArc(oval, 270, 360 * progress / max, true, paint); // 根据进度画圆弧
+ break;
+ }
+ }
+
+ }
+
+ public synchronized int getMax() {
+ return max;
+ }
+
+ /**
+ * 设置进度的最大值
+ *
+ * @param max
+ */
+ public synchronized void setMax(int max) {
+ if (max < 0) {
+ throw new IllegalArgumentException("max not less than 0");
+ }
+ this.max = max;
+ }
+
+ /**
+ * 获取进度.需要同步
+ *
+ * @return
+ */
+ public synchronized int getProgress() {
+ return progress;
+ }
+
+ /**
+ * 设置进度,此为线程安全控件,由于考虑多线的问题,需要同步
+ * 刷新界面调用postInvalidate()能在非UI线程刷新
+ *
+ * @param progress
+ */
+ public synchronized void setProgress(int progress) {
+ if (progress < 0) {
+ throw new IllegalArgumentException("progress not less than 0");
+ }
+ if (progress > max) {
+ progress = max;
+ }
+ if (progress <= max) {
+ this.progress = progress;
+ postInvalidate();
+ }
+
+ }
+
+ public int getCircleColor() {
+ return roundColor;
+ }
+
+ public void setCircleColor(int circleColor) {
+ this.roundColor = circleColor;
+ }
+
+ public int getCircleProgressColor() {
+ return roundProgressColor;
+ }
+
+ public void setCircleProgressColor(int circleProgressColor) {
+ this.roundProgressColor = circleProgressColor;
+ }
+
+ public float getRoundWidth() {
+ return roundWidth;
+ }
+
+ public void setRoundWidth(float roundWidth) {
+ this.roundWidth = roundWidth;
+ }
+
+}
\ No newline at end of file
diff --git a/widget/src/main/java/com/hzecool/widget/svprogresshud/view/SVProgressDefaultView.java b/widget/src/main/java/com/hzecool/widget/svprogresshud/view/SVProgressDefaultView.java
new file mode 100644
index 0000000..de6ced6
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/svprogresshud/view/SVProgressDefaultView.java
@@ -0,0 +1,126 @@
+package com.hzecool.widget.svprogresshud.view;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.RotateAnimation;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.hzecool.widget.R;
+
+
+/**
+ * Created by Sai on 15/8/15.
+ * 默认的SVProgress效果
+ */
+public class SVProgressDefaultView extends LinearLayout {
+ private int resBigLoading = R.mipmap.ic_svstatus_loading;
+ private int resInfo = R.mipmap.ic_svstatus_info;
+ private int resSuccess = R.mipmap.ic_svstatus_success;
+ private int resError = R.mipmap.ic_svstatus_error;
+ private ImageView ivBigLoading, ivSmallLoading;
+ private SVCircleProgressBar circleProgressBar;
+ private TextView tvMsg;
+
+ private RotateAnimation mRotateAnimation;
+
+ public SVProgressDefaultView(Context context) {
+ super(context);
+ initViews();
+ init();
+ }
+
+ private void initViews() {
+ LayoutInflater.from(getContext()).inflate(R.layout.view_svprogressdefault, this, true);
+ ivBigLoading = (ImageView) findViewById(R.id.ivBigLoading);
+ ivSmallLoading = (ImageView) findViewById(R.id.ivSmallLoading);
+ circleProgressBar = (SVCircleProgressBar) findViewById(R.id.circleProgressBar);
+ tvMsg = (TextView) findViewById(R.id.tvMsg);
+ }
+
+ private void init() {
+ mRotateAnimation = new RotateAnimation(0f, 359f,
+ Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
+ mRotateAnimation.setDuration(1000L);
+ mRotateAnimation.setInterpolator(new LinearInterpolator());
+ mRotateAnimation.setRepeatCount(-1);
+ mRotateAnimation.setRepeatMode(Animation.RESTART);
+ }
+
+ public void show() {
+ clearAnimations();
+ ivBigLoading.setImageResource(resBigLoading);
+ ivBigLoading.setVisibility(View.VISIBLE);
+ ivSmallLoading.setVisibility(View.GONE);
+ circleProgressBar.setVisibility(View.GONE);
+ tvMsg.setVisibility(View.GONE);
+ //开启旋转动画
+ ivBigLoading.startAnimation(mRotateAnimation);
+ }
+
+ public void showWithStatus(String string) {
+ if (string == null) {
+ show();
+ return;
+ }
+ showBaseStatus(resBigLoading, string);
+ //开启旋转动画
+ ivSmallLoading.startAnimation(mRotateAnimation);
+ }
+
+ public void showInfoWithStatus(String string) {
+ showBaseStatus(resInfo, string);
+ }
+
+ public void showSuccessWithStatus(String string) {
+ showBaseStatus(resSuccess, string);
+ }
+
+ public void showErrorWithStatus(String string) {
+ showBaseStatus(resError, string);
+ }
+ public void showWithProgress(String string) {
+ showProgress(string);
+ }
+
+ public SVCircleProgressBar getCircleProgressBar() {
+ return circleProgressBar;
+ }
+
+ public void setText(String string){
+ tvMsg.setText(string);
+ }
+
+ public void showProgress(String string) {
+ clearAnimations();
+ tvMsg.setText(string);
+ ivBigLoading.setVisibility(View.GONE);
+ ivSmallLoading.setVisibility(View.GONE);
+ circleProgressBar.setVisibility(View.VISIBLE);
+ tvMsg.setVisibility(View.VISIBLE);
+ }
+
+ public void showBaseStatus(int res, String string) {
+ clearAnimations();
+ ivSmallLoading.setImageResource(res);
+ tvMsg.setText(string);
+ ivBigLoading.setVisibility(View.GONE);
+ circleProgressBar.setVisibility(View.GONE);
+ ivSmallLoading.setVisibility(View.VISIBLE);
+ tvMsg.setVisibility(View.VISIBLE);
+ }
+
+ public void dismiss() {
+ clearAnimations();
+ }
+
+ private void clearAnimations() {
+ ivBigLoading.clearAnimation();
+ ivSmallLoading.clearAnimation();
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/swipdelete/SwipeMenuLayout.java b/widget/src/main/java/com/hzecool/widget/swipdelete/SwipeMenuLayout.java
new file mode 100644
index 0000000..0b4f5ce
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/swipdelete/SwipeMenuLayout.java
@@ -0,0 +1,621 @@
+package com.hzecool.widget.swipdelete;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.PointF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.OvershootInterpolator;
+
+import com.hzecool.widget.R;
+
+/**
+ * Created by tutu on 2017/5/17.
+ */
+
+public class SwipeMenuLayout extends ViewGroup {
+ private static final String TAG = "zxt/SwipeMenuLayout";
+
+ private int mScaleTouchSlop;//为了处理单击事件的冲突
+ private int mMaxVelocity;//计算滑动速度用
+ private int mPointerId;//多点触摸只算第一根手指的速度
+ private int mHeight;//自己的高度
+ //右侧菜单宽度总和(最大滑动距离)
+ private int mRightMenuWidths;
+
+ //滑动判定临界值(右侧菜单宽度的40%) 手指抬起时,超过了展开,没超过收起menu
+ private int mLimit;
+
+ private View mContentView;//2016 11 13 add ,存储contentView(第一个View)
+
+ //private Scroller mScroller;//以前item的滑动动画靠它做,现在用属性动画做
+ //上一次的xy
+ private PointF mLastP = new PointF();
+ //2016 10 22 add , 仿QQ,侧滑菜单展开时,点击除侧滑菜单之外的区域,关闭侧滑菜单。
+ //增加一个布尔值变量,dispatch函数里,每次down时,为true,move时判断,如果是滑动动作,设为false。
+ //在Intercept函数的up时,判断这个变量,如果仍为true 说明是点击事件,则关闭菜单。
+ private boolean isUnMoved = true;
+
+ //2016 11 03 add,判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。
+ //up-down的坐标,判断是否是滑动,如果是,则屏蔽一切点击事件
+ private PointF mFirstP = new PointF();
+ private boolean isUserSwiped;
+
+ //存储的是当前正在展开的View
+ private static SwipeMenuLayout mViewCache;
+
+ //防止多只手指一起滑我的flag 在每次down里判断, touch事件结束清空
+ private static boolean isTouching;
+
+ private VelocityTracker mVelocityTracker;//滑动速度变量
+ private android.util.Log LogUtils;
+
+ /**
+ * 右滑删除功能的开关,默认开
+ */
+ private boolean isSwipeEnable;
+
+ /**
+ * IOS、QQ式交互,默认开
+ */
+ private boolean isIos;
+
+ private boolean iosInterceptFlag;//IOS类型下,是否拦截事件的flag
+
+ /**
+ * 20160929add 左滑右滑的开关,默认左滑打开菜单
+ */
+ private boolean isLeftSwipe;
+
+ public SwipeMenuLayout(Context context) {
+ this(context, null);
+ }
+
+ public SwipeMenuLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SwipeMenuLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs, defStyleAttr);
+ }
+
+ public boolean isSwipeEnable() {
+ return isSwipeEnable;
+ }
+
+ /**
+ * 设置侧滑功能开关
+ *
+ * @param swipeEnable
+ */
+ public void setSwipeEnable(boolean swipeEnable) {
+ isSwipeEnable = swipeEnable;
+ }
+
+
+ public boolean isIos() {
+ return isIos;
+ }
+
+ /**
+ * 设置是否开启IOS阻塞式交互
+ *
+ * @param ios
+ */
+ public SwipeMenuLayout setIos(boolean ios) {
+ isIos = ios;
+ return this;
+ }
+
+ public boolean isLeftSwipe() {
+ return isLeftSwipe;
+ }
+
+ /**
+ * 设置是否开启左滑出菜单,设置false 为右滑出菜单
+ *
+ * @param leftSwipe
+ * @return
+ */
+ public SwipeMenuLayout setLeftSwipe(boolean leftSwipe) {
+ isLeftSwipe = leftSwipe;
+ return this;
+ }
+
+ /**
+ * 返回ViewCache
+ *
+ * @return
+ */
+ public static SwipeMenuLayout getViewCache() {
+ return mViewCache;
+ }
+
+ private void init(Context context, AttributeSet attrs, int defStyleAttr) {
+ mScaleTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mMaxVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();
+ //初始化滑动帮助类对象
+ //mScroller = new Scroller(context);
+
+ //右滑删除功能的开关,默认开
+ isSwipeEnable = true;
+ //IOS、QQ式交互,默认开
+ isIos = true;
+ //左滑右滑的开关,默认左滑打开菜单
+ isLeftSwipe = true;
+ TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SwipeMenuLayout, defStyleAttr, 0);
+ int count = ta.getIndexCount();
+ for (int i = 0; i < count; i++) {
+ int attr = ta.getIndex(i);
+ //如果引用成AndroidLib 资源都不是常量,无法使用switch case
+ if (attr == R.styleable.SwipeMenuLayout_swipeEnable) {
+ isSwipeEnable = ta.getBoolean(attr, true);
+ } else if (attr == R.styleable.SwipeMenuLayout_ios) {
+ isIos = ta.getBoolean(attr, true);
+ } else if (attr == R.styleable.SwipeMenuLayout_leftSwipe) {
+ isLeftSwipe = ta.getBoolean(attr, true);
+ }
+ }
+ ta.recycle();
+
+
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ //Log.d(TAG, "onMeasure() called with: " + "widthMeasureSpec = [" + widthMeasureSpec + "], heightMeasureSpec = [" + heightMeasureSpec + "]");
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ setClickable(true);//令自己可点击,从而获取触摸事件
+
+ mRightMenuWidths = 0;//由于ViewHolder的复用机制,每次这里要手动恢复初始值
+ mHeight = 0;
+ int contentWidth = 0;//2016 11 09 add,适配GridLayoutManager,将以第一个子Item(即ContentItem)的宽度为控件宽度
+ int childCount = getChildCount();
+
+ //add by 2016 08 11 为了子View的高,可以matchParent(参考的FrameLayout 和LinearLayout的Horizontal)
+ final boolean measureMatchParentChildren = MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
+ boolean isNeedMeasureChildHeight = false;
+
+ for (int i = 0; i < childCount; i++) {
+ View childView = getChildAt(i);
+ //令每一个子View可点击,从而获取触摸事件
+ childView.setClickable(true);
+ if (childView.getVisibility() != GONE) {
+ //后续计划加入上滑、下滑,则将不再支持Item的margin
+ measureChild(childView, widthMeasureSpec, heightMeasureSpec);
+ //measureChildWithMargins(childView, widthMeasureSpec, 0, heightMeasureSpec, 0);
+ final MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
+ mHeight = Math.max(mHeight, childView.getMeasuredHeight()/* + lp.topMargin + lp.bottomMargin*/);
+ if (measureMatchParentChildren && lp.height == LayoutParams.MATCH_PARENT) {
+ isNeedMeasureChildHeight = true;
+ }
+ if (i > 0) {//第一个布局是Left item,从第二个开始才是RightMenu
+ mRightMenuWidths += childView.getMeasuredWidth();
+ } else {
+ mContentView = childView;
+ contentWidth = childView.getMeasuredWidth();
+ }
+ }
+ }
+ setMeasuredDimension(getPaddingLeft() + getPaddingRight() + contentWidth,
+ mHeight + getPaddingTop() + getPaddingBottom());//宽度取第一个Item(Content)的宽度
+ mLimit = mRightMenuWidths * 4 / 10;//滑动判断的临界值
+ //Log.d(TAG, "onMeasure() called with: " + "mRightMenuWidths = [" + mRightMenuWidths);
+ if (isNeedMeasureChildHeight) {//如果子View的height有MatchParent属性的,设置子View高度
+ forceUniformHeight(childCount, widthMeasureSpec);
+ }
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new MarginLayoutParams(getContext(), attrs);
+ }
+
+ /**
+ * 给MatchParent的子View设置高度
+ *
+ * @param count
+ * @param widthMeasureSpec
+ * @see android.widget.LinearLayout# 同名方法
+ */
+ private void forceUniformHeight(int count, int widthMeasureSpec) {
+ // Pretend that the linear layout has an exact size. This is the measured height of
+ // ourselves. The measured height should be the max height of the children, changed
+ // to accommodate the heightMeasureSpec from the parent
+ int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(),
+ MeasureSpec.EXACTLY);//以父布局高度构建一个Exactly的测量参数
+ for (int i = 0; i < count; ++i) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+ if (lp.height == LayoutParams.MATCH_PARENT) {
+ // Temporarily force children to reuse their old measured width
+ // FIXME: this may not be right for something like wrapping text?
+ int oldWidth = lp.width;//measureChildWithMargins 这个函数会用到宽,所以要保存一下
+ lp.width = child.getMeasuredWidth();
+ // Remeasure with new dimensions
+ measureChildWithMargins(child, widthMeasureSpec, 0, uniformMeasureSpec, 0);
+ lp.width = oldWidth;
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ //LogUtils.e(TAG, "onLayout() called with: " + "changed = [" + changed + "], l = [" + l + "], t = [" + t + "], r = [" + r + "], b = [" + b + "]");
+ int childCount = getChildCount();
+ int left = 0 + getPaddingLeft();
+ int right = 0 + getPaddingLeft();
+ for (int i = 0; i < childCount; i++) {
+ View childView = getChildAt(i);
+ if (childView.getVisibility() != GONE) {
+ if (i == 0) {//第一个子View是内容 宽度设置为全屏
+ childView.layout(left, getPaddingTop(), left + childView.getMeasuredWidth(), getPaddingTop() + childView.getMeasuredHeight());
+ left = left + childView.getMeasuredWidth();
+ } else {
+ if (isLeftSwipe) {
+ childView.layout(left, getPaddingTop(), left + childView.getMeasuredWidth(), getPaddingTop() + childView.getMeasuredHeight());
+ left = left + childView.getMeasuredWidth();
+ } else {
+ childView.layout(right - childView.getMeasuredWidth(), getPaddingTop(), right, getPaddingTop() + childView.getMeasuredHeight());
+ right = right - childView.getMeasuredWidth();
+ }
+
+ }
+ }
+ }
+ //Log.d(TAG, "onLayout() called with: " + "maxScrollGap = [" + maxScrollGap + "], l = [" + l + "], t = [" + t + "], r = [" + r + "], b = [" + b + "]");
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ //LogUtils.d(TAG, "dispatchTouchEvent() called with: " + "ev = [" + ev + "]");
+ if (isSwipeEnable) {
+ acquireVelocityTracker(ev);
+ final VelocityTracker verTracker = mVelocityTracker;
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ isUserSwiped = false;//2016 11 03 add,判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。
+ isUnMoved = true;//2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。
+ iosInterceptFlag = false;//add by 2016 09 11 ,每次DOWN时,默认是不拦截的
+ if (isTouching) {//如果有别的指头摸过了,那么就return false。这样后续的move..等事件也不会再来找这个View了。
+ return false;
+ } else {
+ isTouching = true;//第一个摸的指头,赶紧改变标志,宣誓主权。
+ }
+ mLastP.set(ev.getRawX(), ev.getRawY());
+ mFirstP.set(ev.getRawX(), ev.getRawY());//2016 11 03 add,判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。
+
+ //如果down,view和cacheview不一样,则立马让它还原。且把它置为null
+ if (mViewCache != null) {
+ if (mViewCache != this) {
+ mViewCache.smoothClose();
+
+ iosInterceptFlag = isIos;//add by 2016 09 11 ,IOS模式开启的话,且当前有侧滑菜单的View,且不是自己的,就该拦截事件咯。
+ }
+ //只要有一个侧滑菜单处于打开状态, 就不给外层布局上下滑动了
+ getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ //求第一个触点的id, 此时可能有多个触点,但至少一个,计算滑动速率用
+ mPointerId = ev.getPointerId(0);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ //add by 2016 09 11 ,IOS模式开启的话,且当前有侧滑菜单的View,且不是自己的,就该拦截事件咯。滑动也不该出现
+ if (iosInterceptFlag) {
+ break;
+ }
+ float gap = mLastP.x - ev.getRawX();
+ //为了在水平滑动中禁止父类ListView等再竖直滑动
+ if (Math.abs(gap) > 10 || Math.abs(getScrollX()) > 10) {//2016 09 29 修改此处,使屏蔽父布局滑动更加灵敏,
+ getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ //2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。begin
+ if (Math.abs(gap) > mScaleTouchSlop) {
+ isUnMoved = false;
+ }
+ //2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。end
+ //如果scroller还没有滑动结束 停止滑动动画
+/* if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }*/
+ scrollBy((int) (gap), 0);//滑动使用scrollBy
+ //越界修正
+ if (isLeftSwipe) {//左滑
+ if (getScrollX() < 0) {
+ scrollTo(0, 0);
+ }
+ if (getScrollX() > mRightMenuWidths) {
+ scrollTo(mRightMenuWidths, 0);
+ }
+ } else {//右滑
+ if (getScrollX() < -mRightMenuWidths) {
+ scrollTo(-mRightMenuWidths, 0);
+ }
+ if (getScrollX() > 0) {
+ scrollTo(0, 0);
+ }
+ }
+
+ mLastP.set(ev.getRawX(), ev.getRawY());
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ //2016 11 03 add,判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。
+ if (Math.abs(ev.getRawX() - mFirstP.x) > mScaleTouchSlop) {
+ isUserSwiped = true;
+ }
+
+ //add by 2016 09 11 ,IOS模式开启的话,且当前有侧滑菜单的View,且不是自己的,就该拦截事件咯。滑动也不该出现
+ if (!iosInterceptFlag) {//且滑动了 才判断是否要收起、展开menu
+ //求伪瞬时速度
+ verTracker.computeCurrentVelocity(1000, mMaxVelocity);
+ final float velocityX = verTracker.getXVelocity(mPointerId);
+ if (Math.abs(velocityX) > 1000) {//滑动速度超过阈值
+ if (velocityX < -1000) {
+ if (isLeftSwipe) {//左滑
+ //平滑展开Menu
+ smoothExpand();
+
+ } else {
+ //平滑关闭Menu
+ smoothClose();
+ }
+ } else {
+ if (isLeftSwipe) {//左滑
+ // 平滑关闭Menu
+ smoothClose();
+ } else {
+ //平滑展开Menu
+ smoothExpand();
+
+ }
+ }
+ } else {
+ if (Math.abs(getScrollX()) > mLimit) {//否则就判断滑动距离
+ //平滑展开Menu
+ smoothExpand();
+ } else {
+ // 平滑关闭Menu
+ smoothClose();
+ }
+ }
+ }
+ //释放
+ releaseVelocityTracker();
+ //LogUtils.i(TAG, "onTouch A ACTION_UP ACTION_CANCEL:velocityY:" + velocityX);
+ isTouching = false;//没有手指在摸我了
+ break;
+ default:
+ break;
+ }
+ }
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ //add by zhangxutong 2016 12 07 begin:
+ //禁止侧滑时,点击事件不受干扰。
+ if (isSwipeEnable) {
+ switch (ev.getAction()) {
+ //add by zhangxutong 2016 11 04 begin :
+ // fix 长按事件和侧滑的冲突。
+ case MotionEvent.ACTION_MOVE:
+ //屏蔽滑动时的事件
+ if (Math.abs(ev.getRawX() - mFirstP.x) > mScaleTouchSlop) {
+ return true;
+ }
+ break;
+ //add by zhangxutong 2016 11 04 end
+ case MotionEvent.ACTION_UP:
+ //为了在侧滑时,屏蔽子View的点击事件
+ if (isLeftSwipe) {
+ if (getScrollX() > mScaleTouchSlop) {
+ //add by 2016 09 10 解决一个智障问题~ 居然不给点击侧滑菜单 我跪着谢罪
+ //这里判断落点在内容区域屏蔽点击,内容区域外,允许传递事件继续向下的的。。。
+ if (ev.getX() < getWidth() - getScrollX()) {
+ //2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。
+ if (isUnMoved) {
+ smoothClose();
+ }
+ return true;//true表示拦截
+ }
+ }
+ } else {
+ if (-getScrollX() > mScaleTouchSlop) {
+ if (ev.getX() > -getScrollX()) {//点击范围在菜单外 屏蔽
+ //2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。
+ if (isUnMoved) {
+ smoothClose();
+ }
+ return true;
+ }
+ }
+ }
+ //add by zhangxutong 2016 11 03 begin:
+ // 判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。
+ if (isUserSwiped) {
+ return true;
+ }
+ //add by zhangxutong 2016 11 03 end
+
+ break;
+ }
+ //模仿IOS 点击其他区域关闭:
+ if (iosInterceptFlag) {
+ //IOS模式开启,且当前有菜单的View,且不是自己的 拦截点击事件给子View
+ return true;
+ }
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ /**
+ * 平滑展开
+ */
+ private ValueAnimator mExpandAnim, mCloseAnim;
+
+ private boolean isExpand;//代表当前是否是展开状态 2016 11 03 add
+
+ public void smoothExpand() {
+ //Log.d(TAG, "smoothExpand() called" + this);
+ /*mScroller.startScroll(getScrollX(), 0, mRightMenuWidths - getScrollX(), 0);
+ invalidate();*/
+ //展开就加入ViewCache:
+ mViewCache = SwipeMenuLayout.this;
+
+ //2016 11 13 add 侧滑菜单展开,屏蔽content长按
+ if (null != mContentView) {
+ mContentView.setLongClickable(false);
+ }
+
+ cancelAnim();
+ mExpandAnim = ValueAnimator.ofInt(getScrollX(), isLeftSwipe ? mRightMenuWidths : -mRightMenuWidths);
+ mExpandAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ scrollTo((Integer) animation.getAnimatedValue(), 0);
+ }
+ });
+ mExpandAnim.setInterpolator(new OvershootInterpolator());
+ mExpandAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ isExpand = true;
+ }
+ });
+ mExpandAnim.setDuration(300).start();
+ }
+
+ /**
+ * 每次执行动画之前都应该先取消之前的动画
+ */
+ private void cancelAnim() {
+ if (mCloseAnim != null && mCloseAnim.isRunning()) {
+ mCloseAnim.cancel();
+ }
+ if (mExpandAnim != null && mExpandAnim.isRunning()) {
+ mExpandAnim.cancel();
+ }
+ }
+
+ /**
+ * 平滑关闭
+ */
+ public void smoothClose() {
+ //Log.d(TAG, "smoothClose() called" + this);
+/* mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0);
+ invalidate();*/
+ mViewCache = null;
+
+ //2016 11 13 add 侧滑菜单展开,屏蔽content长按
+ if (null != mContentView) {
+ mContentView.setLongClickable(true);
+ }
+
+ cancelAnim();
+ mCloseAnim = ValueAnimator.ofInt(getScrollX(), 0);
+ mCloseAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ scrollTo((Integer) animation.getAnimatedValue(), 0);
+ }
+ });
+ mCloseAnim.setInterpolator(new AccelerateInterpolator());
+ mCloseAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ isExpand = false;
+
+ }
+ });
+ mCloseAnim.setDuration(300).start();
+ //LogUtils.d(TAG, "smoothClose() called with:getScrollX() " + getScrollX());
+ }
+
+
+ /**
+ * @param event 向VelocityTracker添加MotionEvent
+ * @see VelocityTracker#obtain()
+ * @see VelocityTracker#addMovement(MotionEvent)
+ */
+ private void acquireVelocityTracker(final MotionEvent event) {
+ if (null == mVelocityTracker) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(event);
+ }
+
+ /**
+ * * 释放VelocityTracker
+ *
+ * @see VelocityTracker#clear()
+ * @see VelocityTracker#recycle()
+ */
+ private void releaseVelocityTracker() {
+ if (null != mVelocityTracker) {
+ mVelocityTracker.clear();
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ //每次ViewDetach的时候,判断一下 ViewCache是不是自己,如果是自己,关闭侧滑菜单,且ViewCache设置为null,
+ // 理由:1 防止内存泄漏(ViewCache是一个静态变量)
+ // 2 侧滑删除后自己后,这个View被Recycler回收,复用,下一个进入屏幕的View的状态应该是普通状态,而不是展开状态。
+ @Override
+ protected void onDetachedFromWindow() {
+ if (this == mViewCache) {
+ mViewCache.smoothClose();
+ mViewCache = null;
+ }
+ super.onDetachedFromWindow();
+ }
+
+ //展开时,禁止长按
+ @Override
+ public boolean performLongClick() {
+ if (Math.abs(getScrollX()) > mScaleTouchSlop) {
+ return false;
+ }
+ return super.performLongClick();
+ }
+
+ //平滑滚动 弃用 改属性动画实现
+/* @Override
+ public void computeScroll() {
+ //判断Scroller是否执行完毕:
+ if (mScroller.computeScrollOffset()) {
+ scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
+ //通知View重绘-invalidate()->onDraw()->computeScroll()
+ invalidate();
+ }
+ }*/
+
+ /**
+ * 快速关闭。
+ * 用于 点击侧滑菜单上的选项,同时想让它快速关闭(删除 置顶)。
+ * 这个方法在ListView里是必须调用的,
+ * 在RecyclerView里,视情况而定,如果是mAdapter.notifyItemRemoved(pos)方法不用调用。
+ */
+ public void quickClose() {
+ if (this == mViewCache) {
+ //先取消展开动画
+ cancelAnim();
+ mViewCache.scrollTo(0, 0);//关闭
+ mViewCache = null;
+ }
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/treeview/holder/SimpleViewHolder.java b/widget/src/main/java/com/hzecool/widget/treeview/holder/SimpleViewHolder.java
new file mode 100644
index 0000000..0e0f6fe
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/treeview/holder/SimpleViewHolder.java
@@ -0,0 +1,29 @@
+package com.hzecool.widget.treeview.holder;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.TextView;
+
+import com.hzecool.widget.treeview.model.TreeNode;
+
+/**
+ * Created by Bogdan Melnychuk on 2/11/15.
+ */
+public class SimpleViewHolder extends TreeNode.BaseNodeViewHolder
+ *
+ * @param topFocus look for a candidate is the one at the top of the bounds
+ * if topFocus is true, or at the bottom of the bounds if topFocus is
+ * false
+ * @param top the top offset of the bounds in which a focusable must be
+ * found
+ * @param bottom the bottom offset of the bounds in which a focusable must
+ * be found
+ * @return the next focusable component in the bounds or null if none can
+ * be found
+ */
+ private View findFocusableViewInBounds(boolean topFocus, int top, int bottom, boolean leftFocus, int left, int right) {
+ List focusables = getFocusables(View.FOCUS_FORWARD);
+ View focusCandidate = null;
+
+ /*
+ * A fully contained focusable is one where its top is below the bound's
+ * top, and its bottom is above the bound's bottom. A partially
+ * contained focusable is one where some part of it is within the
+ * bounds, but it also has some part that is not within bounds. A fully contained
+ * focusable is preferred to a partially contained focusable.
+ */
+ boolean foundFullyContainedFocusable = false;
+
+ int count = focusables.size();
+ for (int i = 0; i < count; i++) {
+ View view = focusables.get(i);
+ int viewTop = view.getTop();
+ int viewBottom = view.getBottom();
+ int viewLeft = view.getLeft();
+ int viewRight = view.getRight();
+
+ if (top < viewBottom && viewTop < bottom && left < viewRight && viewLeft < right) {
+ /*
+ * the focusable is in the target area, it is a candidate for
+ * focusing
+ */
+ final boolean viewIsFullyContained = (top < viewTop) && (viewBottom < bottom) && (left < viewLeft) && (viewRight < right);
+ if (focusCandidate == null) {
+ /* No candidate, take this one */
+ focusCandidate = view;
+ foundFullyContainedFocusable = viewIsFullyContained;
+ } else {
+ final boolean viewIsCloserToVerticalBoundary =
+ (topFocus && viewTop < focusCandidate.getTop()) ||
+ (!topFocus && viewBottom > focusCandidate.getBottom());
+ final boolean viewIsCloserToHorizontalBoundary =
+ (leftFocus && viewLeft < focusCandidate.getLeft()) ||
+ (!leftFocus && viewRight > focusCandidate.getRight());
+ if (foundFullyContainedFocusable) {
+ if (viewIsFullyContained && viewIsCloserToVerticalBoundary && viewIsCloserToHorizontalBoundary) {
+ /*
+ * We're dealing with only fully contained views, so
+ * it has to be closer to the boundary to beat our
+ * candidate
+ */
+ focusCandidate = view;
+ }
+ } else {
+ if (viewIsFullyContained) {
+ /* Any fully contained view beats a partially contained view */
+ focusCandidate = view;
+ foundFullyContainedFocusable = true;
+ } else if (viewIsCloserToVerticalBoundary && viewIsCloserToHorizontalBoundary) {
+ /*
+ * Partially contained view beats another partially
+ * contained view if it's closer
+ */
+ focusCandidate = view;
+ }
+ }
+ }
+ }
+ }
+ return focusCandidate;
+ }
+
+ /**
+ *
Handles scrolling in response to a "home/end" shortcut press. This
+ * method will scroll the view to the top or bottom and give the focus
+ * to the topmost/bottommost component in the new visible area. If no
+ * component is a good candidate for focus, this scrollview reclaims the
+ * focus.
+ *
+ * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
+ * to go the top of the view or
+ * {@link android.view.View#FOCUS_DOWN} to go the bottom
+ * @return true if the key event is consumed by this method, false otherwise
+ */
+ public boolean fullScroll(int direction, boolean horizontal) {
+ if (!horizontal) {
+ boolean down = direction == View.FOCUS_DOWN;
+ int height = getHeight();
+ mTempRect.top = 0;
+ mTempRect.bottom = height;
+ if (down) {
+ int count = getChildCount();
+ if (count > 0) {
+ View view = getChildAt(count - 1);
+ mTempRect.bottom = view.getBottom();
+ mTempRect.top = mTempRect.bottom - height;
+ }
+ }
+ return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom, 0, 0, 0);
+ } else {
+ boolean right = direction == View.FOCUS_DOWN;
+ int width = getWidth();
+ mTempRect.left = 0;
+ mTempRect.right = width;
+ if (right) {
+ int count = getChildCount();
+ if (count > 0) {
+ View view = getChildAt(count - 1);
+ mTempRect.right = view.getBottom();
+ mTempRect.left = mTempRect.right - width;
+ }
+ }
+ return scrollAndFocus(0, 0, 0, direction, mTempRect.top, mTempRect.bottom);
+ }
+ }
+
+ /**
+ *
Scrolls the view to make the area defined by top and
+ * bottom visible. This method attempts to give the focus
+ * to a component visible in this area. If no component can be focused in
+ * the new visible area, the focus is reclaimed by this scrollview.
+ *
+ * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
+ * to go upward
+ * {@link android.view.View#FOCUS_DOWN} to downward
+ * @param top the top offset of the new area to be made visible
+ * @param bottom the bottom offset of the new area to be made visible
+ * @return true if the key event is consumed by this method, false otherwise
+ */
+ private boolean scrollAndFocus(int directionY, int top, int bottom, int directionX, int left, int right) {
+ boolean handled = true;
+ int height = getHeight();
+ int containerTop = getScrollY();
+ int containerBottom = containerTop + height;
+ boolean up = directionY == View.FOCUS_UP;
+ int width = getWidth();
+ int containerLeft = getScrollX();
+ int containerRight = containerLeft + width;
+ boolean leftwards = directionX == View.FOCUS_UP;
+ View newFocused = findFocusableViewInBounds(up, top, bottom, leftwards, left, right);
+ if (newFocused == null) {
+ newFocused = this;
+ }
+ if ((top >= containerTop && bottom <= containerBottom) || (left >= containerLeft && right <= containerRight)) {
+ handled = false;
+ } else {
+ int deltaY = up ? (top - containerTop) : (bottom - containerBottom);
+ int deltaX = leftwards ? (left - containerLeft) : (right - containerRight);
+ doScroll(deltaX, deltaY);
+ }
+ if (newFocused != findFocus() && newFocused.requestFocus(directionY)) {
+ mTwoDScrollViewMovedFocus = true;
+ mTwoDScrollViewMovedFocus = false;
+ }
+ return handled;
+ }
+
+ /**
+ * Handle scrolling in response to an up or down arrow click.
+ *
+ * @param direction The direction corresponding to the arrow key that was
+ * pressed
+ * @return True if we consumed the event, false otherwise
+ */
+ public boolean arrowScroll(int direction, boolean horizontal) {
+ View currentFocused = findFocus();
+ if (currentFocused == this) currentFocused = null;
+ View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
+ final int maxJump = horizontal ? getMaxScrollAmountHorizontal() : getMaxScrollAmountVertical();
+
+ if (!horizontal) {
+ if (nextFocused != null) {
+ nextFocused.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(nextFocused, mTempRect);
+ int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+ doScroll(0, scrollDelta);
+ nextFocused.requestFocus(direction);
+ } else {
+ // no new focus
+ int scrollDelta = maxJump;
+ if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {
+ scrollDelta = getScrollY();
+ } else if (direction == View.FOCUS_DOWN) {
+ if (getChildCount() > 0) {
+ int daBottom = getChildAt(0).getBottom();
+ int screenBottom = getScrollY() + getHeight();
+ if (daBottom - screenBottom < maxJump) {
+ scrollDelta = daBottom - screenBottom;
+ }
+ }
+ }
+ if (scrollDelta == 0) {
+ return false;
+ }
+ doScroll(0, direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
+ }
+ } else {
+ if (nextFocused != null) {
+ nextFocused.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(nextFocused, mTempRect);
+ int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+ doScroll(scrollDelta, 0);
+ nextFocused.requestFocus(direction);
+ } else {
+ // no new focus
+ int scrollDelta = maxJump;
+ if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {
+ scrollDelta = getScrollY();
+ } else if (direction == View.FOCUS_DOWN) {
+ if (getChildCount() > 0) {
+ int daBottom = getChildAt(0).getBottom();
+ int screenBottom = getScrollY() + getHeight();
+ if (daBottom - screenBottom < maxJump) {
+ scrollDelta = daBottom - screenBottom;
+ }
+ }
+ }
+ if (scrollDelta == 0) {
+ return false;
+ }
+ doScroll(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta, 0);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Smooth scroll by a Y delta
+ *
+ * @param delta the number of pixels to scroll by on the Y axis
+ */
+ private void doScroll(int deltaX, int deltaY) {
+ if (deltaX != 0 || deltaY != 0) {
+ smoothScrollBy(deltaX, deltaY);
+ }
+ }
+
+ /**
+ * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
+ *
+ * @param dx the number of pixels to scroll by on the X axis
+ * @param dy the number of pixels to scroll by on the Y axis
+ */
+ public final void smoothScrollBy(int dx, int dy) {
+ long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
+ if (duration > ANIMATED_SCROLL_GAP) {
+ mScroller.startScroll(getScrollX(), getScrollY(), dx, dy);
+ awakenScrollBars(mScroller.getDuration());
+ invalidate();
+ } else {
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+ scrollBy(dx, dy);
+ }
+ mLastScroll = AnimationUtils.currentAnimationTimeMillis();
+ }
+
+ /**
+ * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
+ *
+ * @param x the position where to scroll on the X axis
+ * @param y the position where to scroll on the Y axis
+ */
+ public final void smoothScrollTo(int x, int y) {
+ smoothScrollBy(x - getScrollX(), y - getScrollY());
+ }
+
+ /**
+ *
The scroll range of a scroll view is the overall height of all of its
+ * children.
+ */
+ @Override
+ protected int computeVerticalScrollRange() {
+ int count = getChildCount();
+ return count == 0 ? getHeight() : (getChildAt(0)).getBottom();
+ }
+
+ @Override
+ protected int computeHorizontalScrollRange() {
+ int count = getChildCount();
+ return count == 0 ? getWidth() : (getChildAt(0)).getRight();
+ }
+
+ @Override
+ protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
+ ViewGroup.LayoutParams lp = child.getLayoutParams();
+ int childWidthMeasureSpec;
+ int childHeightMeasureSpec;
+
+ childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, getPaddingLeft() + getPaddingRight(), lp.width);
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ @Override
+ protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+ final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);
+ final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ @Override
+ public void computeScroll() {
+ if (mScroller.computeScrollOffset()) {
+ // This is called at drawing time by ViewGroup. We don't want to
+ // re-show the scrollbars at this point, which scrollTo will do,
+ // so we replicate most of scrollTo here.
+ //
+ // It's a little odd to call onScrollChanged from inside the drawing.
+ //
+ // It is, except when you remember that computeScroll() is used to
+ // animate scrolling. So unless we want to defer the onScrollChanged()
+ // until the end of the animated scrolling, we don't really have a
+ // choice here.
+ //
+ // I agree. The alternative, which I think would be worse, is to post
+ // something and tell the subclasses later. This is bad because there
+ // will be a window where mScrollX/Y is different from what the app
+ // thinks it is.
+ //
+ int oldX = getScrollX();
+ int oldY = getScrollY();
+ int x = mScroller.getCurrX();
+ int y = mScroller.getCurrY();
+ if (getChildCount() > 0) {
+ View child = getChildAt(0);
+ scrollTo(clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth()),
+ clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight()));
+ } else {
+ scrollTo(x, y);
+ }
+ if (oldX != getScrollX() || oldY != getScrollY()) {
+ onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);
+ }
+
+ // Keep on drawing until the animation has finished.
+ postInvalidate();
+ }
+ }
+
+ /**
+ * Scrolls the view to the given child.
+ *
+ * @param child the View to scroll to
+ */
+ private void scrollToChild(View child) {
+ child.getDrawingRect(mTempRect);
+ /* Offset from child's local coordinates to TwoDScrollView coordinates */
+ offsetDescendantRectToMyCoords(child, mTempRect);
+ int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+ if (scrollDelta != 0) {
+ scrollBy(0, scrollDelta);
+ }
+ }
+
+ /**
+ * If rect is off screen, scroll just enough to get it (or at least the
+ * first screen size chunk of it) on screen.
+ *
+ * @param rect The rectangle.
+ * @param immediate True to scroll immediately without animation
+ * @return true if scrolling was performed
+ */
+ private boolean scrollToChildRect(Rect rect, boolean immediate) {
+ final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
+ final boolean scroll = delta != 0;
+ if (scroll) {
+ if (immediate) {
+ scrollBy(0, delta);
+ } else {
+ smoothScrollBy(0, delta);
+ }
+ }
+ return scroll;
+ }
+
+ /**
+ * Compute the amount to scroll in the Y direction in order to get
+ * a rectangle completely on the screen (or, if taller than the screen,
+ * at least the first screen size chunk of it).
+ *
+ * @param rect The rect.
+ * @return The scroll delta.
+ */
+ protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
+ if (getChildCount() == 0) return 0;
+ int height = getHeight();
+ int screenTop = getScrollY();
+ int screenBottom = screenTop + height;
+ int fadingEdge = getVerticalFadingEdgeLength();
+ // leave room for top fading edge as long as rect isn't at very top
+ if (rect.top > 0) {
+ screenTop += fadingEdge;
+ }
+
+ // leave room for bottom fading edge as long as rect isn't at very bottom
+ if (rect.bottom < getChildAt(0).getHeight()) {
+ screenBottom -= fadingEdge;
+ }
+ int scrollYDelta = 0;
+ if (rect.bottom > screenBottom && rect.top > screenTop) {
+ // need to move down to get it in view: move down just enough so
+ // that the entire rectangle is in view (or at least the first
+ // screen size chunk).
+ if (rect.height() > height) {
+ // just enough to get screen size chunk on
+ scrollYDelta += (rect.top - screenTop);
+ } else {
+ // get entire rect at bottom of screen
+ scrollYDelta += (rect.bottom - screenBottom);
+ }
+
+ // make sure we aren't scrolling beyond the end of our content
+ int bottom = getChildAt(0).getBottom();
+ int distanceToBottom = bottom - screenBottom;
+ scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
+
+ } else if (rect.top < screenTop && rect.bottom < screenBottom) {
+ // need to move up to get it in view: move up just enough so that
+ // entire rectangle is in view (or at least the first screen
+ // size chunk of it).
+
+ if (rect.height() > height) {
+ // screen size chunk
+ scrollYDelta -= (screenBottom - rect.bottom);
+ } else {
+ // entire rect at top
+ scrollYDelta -= (screenTop - rect.top);
+ }
+
+ // make sure we aren't scrolling any further than the top our content
+ scrollYDelta = Math.max(scrollYDelta, -getScrollY());
+ }
+ return scrollYDelta;
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ if (!mTwoDScrollViewMovedFocus) {
+ if (!mIsLayoutDirty) {
+ scrollToChild(focused);
+ } else {
+ // The child may not be laid out yet, we can't compute the scroll yet
+ mChildToScrollTo = focused;
+ }
+ }
+ super.requestChildFocus(child, focused);
+ }
+
+ /**
+ * When looking for focus in children of a scroll view, need to be a little
+ * more careful not to give focus to something that is scrolled off screen.
+ * This is more expensive than the default {@link android.view.ViewGroup}
+ * implementation, otherwise this behavior might have been made the default.
+ */
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ // convert from forward / backward notation to up / down / left / right
+ // (ugh).
+ if (direction == View.FOCUS_FORWARD) {
+ direction = View.FOCUS_DOWN;
+ } else if (direction == View.FOCUS_BACKWARD) {
+ direction = View.FOCUS_UP;
+ }
+
+ final View nextFocus = previouslyFocusedRect == null ?
+ FocusFinder.getInstance().findNextFocus(this, null, direction) :
+ FocusFinder.getInstance().findNextFocusFromRect(this,
+ previouslyFocusedRect, direction);
+
+ if (nextFocus == null) {
+ return false;
+ }
+
+ return nextFocus.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ @Override
+ public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+ // offset into coordinate space of this scroll view
+ rectangle.offset(child.getLeft() - child.getScrollX(), child.getTop() - child.getScrollY());
+ return scrollToChildRect(rectangle, immediate);
+ }
+
+ @Override
+ public void requestLayout() {
+ mIsLayoutDirty = true;
+ super.requestLayout();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ mIsLayoutDirty = false;
+ // Give a child focus if it needs it
+ if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
+ scrollToChild(mChildToScrollTo);
+ }
+ mChildToScrollTo = null;
+
+ // Calling this with the present values causes it to re-clam them
+ scrollTo(getScrollX(), getScrollY());
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ View currentFocused = findFocus();
+ if (null == currentFocused || this == currentFocused)
+ return;
+
+ // If the currently-focused view was visible on the screen when the
+ // screen was at the old height, then scroll the screen to make that
+ // view visible with the new screen height.
+ currentFocused.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(currentFocused, mTempRect);
+ int scrollDeltaX = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+ int scrollDeltaY = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+ doScroll(scrollDeltaX, scrollDeltaY);
+ }
+
+ /**
+ * Return true if child is an descendant of parent, (or equal to the parent).
+ */
+ private boolean isViewDescendantOf(View child, View parent) {
+ if (child == parent) {
+ return true;
+ }
+
+ final ViewParent theParent = child.getParent();
+ return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
+ }
+
+ /**
+ * Fling the scroll view
+ *
+ * @param velocityY The initial velocity in the Y direction. Positive
+ * numbers mean that the finger/curor is moving down the screen,
+ * which means we want to scroll towards the top.
+ */
+ public void fling(int velocityX, int velocityY) {
+ if (getChildCount() > 0) {
+ int height = getHeight() - getPaddingBottom() - getPaddingTop();
+ int bottom = getChildAt(0).getHeight();
+ int width = getWidth() - getPaddingRight() - getPaddingLeft();
+ int right = getChildAt(0).getWidth();
+
+ mScroller.fling(getScrollX(), getScrollY(), velocityX, velocityY, 0, right - width, 0, bottom - height);
+
+ final boolean movingDown = velocityY > 0;
+ final boolean movingRight = velocityX > 0;
+
+ View newFocused = findFocusableViewInMyBounds(movingRight, mScroller.getFinalX(), movingDown, mScroller.getFinalY(), findFocus());
+ if (newFocused == null) {
+ newFocused = this;
+ }
+
+ if (newFocused != findFocus() && newFocused.requestFocus(movingDown ? View.FOCUS_DOWN : View.FOCUS_UP)) {
+ mTwoDScrollViewMovedFocus = true;
+ mTwoDScrollViewMovedFocus = false;
+ }
+
+ awakenScrollBars(mScroller.getDuration());
+ invalidate();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * This version also clamps the scrolling to the bounds of our child.
+ */
+ public void scrollTo(int x, int y) {
+ // we rely on the fact the View.scrollBy calls scrollTo.
+ if (getChildCount() > 0) {
+ View child = getChildAt(0);
+ x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());
+ y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());
+ if (x != getScrollX() || y != getScrollY()) {
+ super.scrollTo(x, y);
+ }
+ }
+ }
+
+ private int clamp(int n, int my, int child) {
+ if (my >= child || n < 0) {
+ /* my >= child is this case:
+ * |--------------- me ---------------|
+ * |------ child ------|
+ * or
+ * |--------------- me ---------------|
+ * |------ child ------|
+ * or
+ * |--------------- me ---------------|
+ * |------ child ------|
+ *
+ * n < 0 is this case:
+ * |------ me ------|
+ * |-------- child --------|
+ * |-- mScrollX --|
+ */
+ return 0;
+ }
+ if ((my + n) > child) {
+ /* this case:
+ * |------ me ------|
+ * |------ child ------|
+ * |-- mScrollX --|
+ */
+ return child - my;
+ }
+ return n;
+ }
+}
diff --git a/widget/src/main/java/com/hzecool/widget/utils/GlideSetting.java b/widget/src/main/java/com/hzecool/widget/utils/GlideSetting.java
new file mode 100644
index 0000000..cec9a5e
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/utils/GlideSetting.java
@@ -0,0 +1,22 @@
+package com.hzecool.widget.utils;
+
+import com.bumptech.glide.Priority;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.bumptech.glide.request.RequestOptions;
+
+/**
+ * Created by 47066 on 2017/9/7.
+ */
+
+public class GlideSetting {
+ public static RequestOptions getGlideSetting() {
+ RequestOptions options = new RequestOptions()
+ .centerCrop()
+ //.placeholder(R.drawable.placeholder)
+ //.error(R.drawable.error)
+ .priority(Priority.HIGH)
+ .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC);
+ return options;
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/utils/ImageUtils.java b/widget/src/main/java/com/hzecool/widget/utils/ImageUtils.java
new file mode 100644
index 0000000..b92d95b
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/utils/ImageUtils.java
@@ -0,0 +1,146 @@
+package com.hzecool.widget.utils;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.ThumbnailUtils;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+/**
+ * 图片简单处理工具类
+ */
+public class ImageUtils {
+
+ /**
+ * 屏幕宽
+ *
+ * @param context
+ * @return
+ */
+ public static int getWidth(Context context) {
+ DisplayMetrics dm = context.getResources().getDisplayMetrics();
+ return dm.widthPixels;
+ }
+
+ /**
+ * 屏幕高
+ *
+ * @param context
+ * @return
+ */
+ public static int getHeight(Context context) {
+ DisplayMetrics dm = context.getResources().getDisplayMetrics();
+ return dm.heightPixels;
+ }
+
+ /**
+ * 解决小米、魅族等定制ROM
+ * @param context
+ * @param intent
+ * @return
+ */
+ public static Uri getUri(Context context , Intent intent) {
+ Uri uri = intent.getData();
+ String type = intent.getType();
+ if (uri.getScheme().equals("file") && (type.contains("image/"))) {
+ String path = uri.getEncodedPath();
+ if (path != null) {
+ path = Uri.decode(path);
+ ContentResolver cr = context.getContentResolver();
+ StringBuffer buff = new StringBuffer();
+ buff.append("(").append(Images.ImageColumns.DATA).append("=")
+ .append("'" + path + "'").append(")");
+ Cursor cur = cr.query(Images.Media.EXTERNAL_CONTENT_URI,
+ new String[] { Images.ImageColumns._ID },
+ buff.toString(), null, null);
+ int index = 0;
+ for (cur.moveToFirst(); !cur.isAfterLast(); cur.moveToNext()) {
+ index = cur.getColumnIndex(Images.ImageColumns._ID);
+ // set _id value
+ index = cur.getInt(index);
+ }
+ if (index == 0) {
+ // do nothing
+ } else {
+ Uri uri_temp = Uri
+ .parse("content://media/external/images/media/"
+ + index);
+ if (uri_temp != null) {
+ uri = uri_temp;
+ Log.i("urishi", uri.toString());
+ }
+ }
+ }
+ }
+ return uri;
+ }
+
+ /**
+ * 根据文件Uri获取路径
+ *
+ * @param context
+ * @param uri
+ * @return
+ */
+ public static String getFilePathByFileUri(Context context, Uri uri) {
+ String filePath = null;
+ Cursor cursor = context.getContentResolver().query(uri, null, null,
+ null, null);
+ if (cursor.moveToFirst()) {
+ filePath = cursor.getString(cursor
+ .getColumnIndex(MediaStore.Images.Media.DATA));
+ }
+ cursor.close();
+ return filePath;
+ }
+
+ /**
+ * 根据图片原始路径获取图片缩略图
+ *
+ * @param imagePath 图片原始路径
+ * @param width 缩略图宽度
+ * @param height 缩略图高度
+ * @return
+ */
+ public static Bitmap getImageThumbnail(String imagePath, int width,
+ int height) {
+ Bitmap bitmap = null;
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;//不加载直接获取Bitmap宽高
+ // 获取这个图片的宽和高,注意此处的bitmap为null
+ bitmap = BitmapFactory.decodeFile(imagePath, options);
+ if(bitmap == null){
+ // 计算缩放比
+ int h = options.outHeight;
+ int w = options.outWidth;
+ Log.i("test", "optionsH"+h+"optionsW"+w);
+ int beWidth = w / width;
+ int beHeight = h / height;
+ int rate = 1;
+ if (beWidth < beHeight) {
+ rate = beWidth;
+ } else {
+ rate = beHeight;
+ }
+ if (rate <= 0) {//图片实际大小小于缩略图,不缩放
+ rate = 1;
+ }
+ options.inSampleSize = rate;
+ options.inJustDecodeBounds = false;
+ // 重新读入图片,读取缩放后的bitmap,注意这次要把options.inJustDecodeBounds 设为 false
+ bitmap = BitmapFactory.decodeFile(imagePath, options);
+ // 利用ThumbnailUtils来创建缩略图,这里要指定要缩放哪个Bitmap对象
+ bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height,
+ ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
+ }
+ return bitmap;
+ }
+
+}
diff --git a/widget/src/main/java/com/hzecool/widget/utils/SizeUtils.java b/widget/src/main/java/com/hzecool/widget/utils/SizeUtils.java
new file mode 100644
index 0000000..6d3135a
--- /dev/null
+++ b/widget/src/main/java/com/hzecool/widget/utils/SizeUtils.java
@@ -0,0 +1,179 @@
+package com.hzecool.widget.utils;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ *