) {
28 | this.images = images
29 | notifyDataSetChanged()
30 | }
31 |
32 | override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ImageViewHolder {
33 | return ImageViewHolder(LayoutInflater.from(parent?.context).inflate(R.layout.item_image, parent, false))
34 | }
35 |
36 | override fun onBindViewHolder(holder: ImageViewHolder?, position: Int) {
37 | holder?.imageView?.setOnClickListener({
38 | listener?.onItemClick(position)
39 | })
40 | Glide.with(holder?.imageView?.context)
41 | .load(Uri.fromFile(File(images[position].path)))
42 | .into(holder?.imageView)
43 |
44 | }
45 |
46 | override fun getItemCount(): Int {
47 | return images.size
48 | }
49 |
50 | inner class ImageViewHolder constructor(var rootView: View) : RecyclerView.ViewHolder(rootView) {
51 | val imageView: ImageView = rootView.findViewById(R.id.iv) as ImageView
52 | init {
53 | rootView.layoutParams = AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, Utils.getImageItemWidth(rootView.context as Activity))
54 | }
55 | }
56 |
57 | interface OnItemClickListener {
58 | fun onItemClick(position: Int)
59 | }
60 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/huburt/imagepicker/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.imagepicker
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import android.support.v7.widget.GridLayoutManager
6 | import com.huburt.library.ImagePicker
7 | import com.huburt.library.bean.ImageItem
8 | import com.huburt.library.util.Utils
9 | import com.huburt.library.view.GridSpacingItemDecoration
10 | import kotlinx.android.synthetic.main.activity_main.*
11 |
12 | /**
13 | * Created by hubert
14 | *
15 | * Created on 2017/10/31.
16 | *
17 | *
18 | */
19 | class MainActivity : AppCompatActivity(), ImagePicker.OnPickImageResultListener {
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | setContentView(R.layout.activity_main)
24 | //使用自定义默认参数或者默认参数,并清除Application启动之后选择图片缓存
25 | ImagePicker.defaultConfig()
26 | //临时改变选择图片参数
27 | // ImagePicker.limit(12);
28 | //默认不裁剪
29 | cb_crop.setOnCheckedChangeListener({ _, isChecked -> ImagePicker.isCrop(isChecked) })
30 | cb_multi.isChecked = true//默认是多选
31 | cb_multi.setOnCheckedChangeListener { _, isChecked -> ImagePicker.multiMode(isChecked) }
32 | btn_pick.setOnClickListener {
33 | //选择图片,第二次进入会自动带入之前选择的图片(未重置图片参数)
34 | ImagePicker.pick(this@MainActivity, this@MainActivity)
35 | }
36 | btn_camera.setOnClickListener {
37 | //直接打开相机
38 | ImagePicker.camera(this@MainActivity, this@MainActivity)
39 | }
40 | recycler_view.layoutManager = GridLayoutManager(this, 3)
41 | val imageAdapter = ImageAdapter(ArrayList())
42 | imageAdapter.listener = object : ImageAdapter.OnItemClickListener {
43 | override fun onItemClick(position: Int) {
44 | //回顾已选择图片,可以删除
45 | ImagePicker.review(this@MainActivity, position, this@MainActivity)
46 | }
47 | }
48 | recycler_view.addItemDecoration(GridSpacingItemDecoration(3, Utils.dp2px(this, 2f), false))
49 | recycler_view.adapter = imageAdapter
50 | }
51 |
52 | override fun onImageResult(imageItems: ArrayList) {
53 | (recycler_view.adapter as ImageAdapter).updateData(imageItems)
54 | }
55 |
56 | override fun onDestroy() {
57 | super.onDestroy()
58 | ImagePicker.clear()//清除缓存已选择的图片
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/huburt/imagepicker/MainJavaActivity.java:
--------------------------------------------------------------------------------
1 | package com.huburt.imagepicker;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.support.v7.widget.GridLayoutManager;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.view.View;
9 | import android.widget.Button;
10 | import android.widget.CheckBox;
11 | import android.widget.CompoundButton;
12 |
13 | import com.huburt.library.ImagePicker;
14 | import com.huburt.library.bean.ImageItem;
15 | import com.huburt.library.util.Utils;
16 | import com.huburt.library.view.GridSpacingItemDecoration;
17 |
18 | import org.jetbrains.annotations.NotNull;
19 |
20 | import java.util.ArrayList;
21 |
22 | /**
23 | * Created by hubert
24 | *
25 | * Created on 2017/10/31.
26 | *
27 | * 这个类与MainActivity的java实现,内容完全相同
28 | */
29 |
30 | public class MainJavaActivity extends AppCompatActivity implements ImagePicker.OnPickImageResultListener {
31 |
32 | private ImageAdapter adapter;
33 |
34 | @Override
35 | protected void onCreate(@Nullable Bundle savedInstanceState) {
36 | super.onCreate(savedInstanceState);
37 | setContentView(R.layout.activity_main);
38 | //使用自定义默认参数或者默认参数
39 | ImagePicker.defaultConfig();
40 | //临时改变选择图片参数
41 | // ImagePicker.limit(12);
42 | //默认不裁剪
43 | CheckBox cb_crop = (CheckBox) findViewById(R.id.cb_crop);
44 | cb_crop.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
45 | @Override
46 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
47 | ImagePicker.isCrop(isChecked);
48 | }
49 | });
50 | CheckBox cb_multi = (CheckBox) findViewById(R.id.cb_multi);
51 | cb_multi.setChecked(true);//默认是多选
52 | cb_multi.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
53 | @Override
54 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
55 | ImagePicker.multiMode(isChecked);
56 | }
57 | });
58 | Button btn_pick = (Button) findViewById(R.id.btn_pick);
59 | btn_pick.setOnClickListener(new View.OnClickListener() {
60 | @Override
61 | public void onClick(View v) {
62 | //选择图片,第二次进入会自动带入之前选择的图片(未重置图片参数)
63 | ImagePicker.pick(MainJavaActivity.this, MainJavaActivity.this);
64 | }
65 | });
66 | Button btn_camera = (Button) findViewById(R.id.btn_camera);
67 | btn_camera.setOnClickListener(new View.OnClickListener() {
68 | @Override
69 | public void onClick(View v) {
70 | //直接打开相机
71 | ImagePicker.camera(MainJavaActivity.this, MainJavaActivity.this);
72 | }
73 | });
74 | RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
75 | recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
76 | adapter = new ImageAdapter(new ArrayList());
77 | adapter.setListener(new ImageAdapter.OnItemClickListener() {
78 | @Override
79 | public void onItemClick(int position) {
80 | //回顾已选择图片,可以删除
81 | ImagePicker.review(MainJavaActivity.this, position, MainJavaActivity.this);
82 | }
83 | });
84 | recyclerView.addItemDecoration(new GridSpacingItemDecoration(3, Utils.dp2px(this, 2f), false));
85 | recyclerView.setAdapter(adapter);
86 | }
87 |
88 | @Override
89 | public void onImageResult(@NotNull ArrayList imageItems) {
90 | adapter.updateData(imageItems);
91 | }
92 |
93 | @Override
94 | protected void onDestroy() {
95 | super.onDestroy();
96 | ImagePicker.clear();//清除缓存已选择的图片
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
19 |
20 |
26 |
27 |
28 |
32 |
33 |
38 |
39 |
44 |
45 |
46 |
47 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ImagePicker
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.1.51'
5 | repositories {
6 | jcenter()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:2.3.3'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
12 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | maven { url "https://jitpack.io" }
20 | }
21 | }
22 |
23 | task clean(type: Delete) {
24 | delete rootProject.buildDir
25 | }
26 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Oct 26 11:18:07 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | apply plugin: 'com.github.dcendents.android-maven'
5 | group='com.github.huburt-Hu'
6 |
7 | android {
8 | compileSdkVersion 25
9 | buildToolsVersion "25.0.3"
10 |
11 | defaultConfig {
12 | minSdkVersion 16
13 | targetSdkVersion 25
14 | versionCode 1
15 | versionName "1.0"
16 | vectorDrawables.useSupportLibrary = true
17 | }
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | }
25 |
26 | dependencies {
27 | compile fileTree(include: ['*.jar'], dir: 'libs')
28 | compile 'com.android.support:appcompat-v7:25.3.1'
29 | compile 'com.android.support:recyclerview-v7:25.3.1'
30 | compile 'com.android.support:support-v4:25.3.1'
31 | compile 'com.github.chrisbanes:PhotoView:1.3.1'
32 | compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
33 |
34 | }
35 | repositories {
36 | mavenCentral()
37 | }
38 |
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/julie/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
22 |
23 |
26 |
27 |
30 |
31 |
34 |
35 |
40 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/C.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library
2 |
3 | /**
4 | * Created by hubert
5 | *
6 | *
7 | * Created on 2017/10/19.
8 | */
9 |
10 | object C {
11 | val EXTRA_IMAGE_ITEMS = "extra_image_items"
12 |
13 | val EXTRA_POSITION = "extra_position"
14 |
15 | val EXTRA_TYPE = "extra_type"
16 |
17 | val EXTRA_TAKE_PHOTO = "extra_take_photo"
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/Delegates.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library
2 |
3 | import kotlin.properties.ReadWriteProperty
4 | import kotlin.reflect.KProperty
5 |
6 | /**
7 | * Created by hubert
8 | *
9 | * Created on 2017/10/31.
10 | */
11 | internal class InitializationCheck(private val message: String? = null) : ReadWriteProperty {
12 | private var value: T? = null
13 | override fun getValue(thisRef: Any?, property: KProperty<*>): T {
14 | return value ?: throw IllegalStateException(message ?: "not initialized")
15 | }
16 |
17 | override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
18 | this.value = value
19 | }
20 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/ImageDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library
2 |
3 | import android.database.Cursor
4 | import android.os.Bundle
5 | import android.provider.MediaStore
6 | import android.support.v4.app.FragmentActivity
7 | import android.support.v4.app.LoaderManager
8 | import android.support.v4.content.CursorLoader
9 | import android.support.v4.content.Loader
10 |
11 | import com.huburt.library.bean.ImageFolder
12 | import com.huburt.library.bean.ImageItem
13 |
14 | import java.io.File
15 | import java.util.ArrayList
16 |
17 | class ImageDataSource(private val activity: FragmentActivity) : LoaderManager.LoaderCallbacks {
18 |
19 | private val IMAGE_PROJECTION = arrayOf(//查询图片需要的数据列
20 | MediaStore.Images.Media.DISPLAY_NAME, //图片的显示名称 aaa.jpg
21 | MediaStore.Images.Media.DATA, //图片的真实路径 /storage/emulated/0/pp/downloader/wallpaper/aaa.jpg
22 | MediaStore.Images.Media.SIZE, //图片的大小,long型 132492
23 | MediaStore.Images.Media.WIDTH, //图片的宽度,int型 1920
24 | MediaStore.Images.Media.HEIGHT, //图片的高度,int型 1080
25 | MediaStore.Images.Media.MIME_TYPE, //图片的类型 image/jpeg
26 | MediaStore.Images.Media.DATE_ADDED) //图片被添加的时间,long型 1450518608
27 | private var loadedListener: OnImagesLoadedListener? = null //图片加载完成的回调接口
28 | private val imageFolders = ArrayList() //所有的图片文件夹
29 | private var currentMode: Int? = null
30 |
31 | fun loadImage(loadedListener: OnImagesLoadedListener) {
32 | loadImage(null, loadedListener)
33 | }
34 |
35 | /**
36 | * @param path 指定扫描的文件夹目录,可以为 null,表示扫描所有图片
37 | * @param loadedListener 图片加载完成的监听
38 | */
39 | fun loadImage(path: String?, loadedListener: OnImagesLoadedListener) {
40 | this.loadedListener = loadedListener
41 | destroyLoader()
42 |
43 | val loaderManager = activity.supportLoaderManager
44 | val bundle = Bundle()
45 | if (path == null) {
46 | currentMode = LOADER_ALL
47 | loaderManager.initLoader(LOADER_ALL, bundle, this)//加载所有的图片
48 | } else {
49 | currentMode = LOADER_CATEGORY
50 | //加载指定目录的图片
51 | bundle.putString("path", path)
52 | loaderManager.initLoader(LOADER_CATEGORY, bundle, this)
53 | }
54 | }
55 |
56 | override fun onCreateLoader(id: Int, args: Bundle): Loader? {
57 | var cursorLoader: CursorLoader? = null
58 | //扫描所有图片
59 | if (id == LOADER_ALL)
60 | cursorLoader = CursorLoader(activity, MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
61 | IMAGE_PROJECTION, null, null, IMAGE_PROJECTION[6] + " DESC")
62 | //扫描某个图片文件夹
63 | if (id == LOADER_CATEGORY)
64 | cursorLoader = CursorLoader(activity, MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
65 | IMAGE_PROJECTION, IMAGE_PROJECTION[1] + " like '%" + args.getString("path") + "%'",
66 | null, IMAGE_PROJECTION[6] + " DESC")
67 |
68 | return cursorLoader
69 | }
70 |
71 | override fun onLoadFinished(loader: Loader, data: Cursor?) {
72 | imageFolders.clear()
73 | if (data != null) {
74 | val allImages = ArrayList() //所有图片的集合,不分文件夹
75 | while (data.moveToNext()) {
76 | //查询数据
77 | val imageName = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[0]))
78 | val imagePath = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[1]))
79 |
80 | val file = File(imagePath)
81 | if (!file.exists() || file.length() <= 0) {
82 | continue
83 | }
84 |
85 | val imageSize = data.getLong(data.getColumnIndexOrThrow(IMAGE_PROJECTION[2]))
86 | val imageWidth = data.getInt(data.getColumnIndexOrThrow(IMAGE_PROJECTION[3]))
87 | val imageHeight = data.getInt(data.getColumnIndexOrThrow(IMAGE_PROJECTION[4]))
88 | val imageMimeType = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[5]))
89 | val imageAddTime = data.getLong(data.getColumnIndexOrThrow(IMAGE_PROJECTION[6]))
90 | //封装实体
91 | val imageItem = ImageItem(imagePath)
92 | imageItem.size = imageSize
93 | imageItem.addTime = imageAddTime
94 | imageItem.height = imageHeight
95 | imageItem.width = imageWidth
96 | imageItem.mimeType = imageMimeType
97 | allImages.add(imageItem)
98 | //根据父路径分类存放图片
99 | val imageFile = File(imagePath)
100 | val imageParentFile = imageFile.parentFile
101 | val imageFolder = ImageFolder(imageParentFile.name, imageParentFile.absolutePath)
102 |
103 | if (!imageFolders.contains(imageFolder)) {
104 | val images = ArrayList()
105 | images.add(imageItem)
106 | imageFolder.cover = imageItem
107 | imageFolder.images = images
108 | imageFolders.add(imageFolder)
109 | } else {
110 | imageFolders[imageFolders.indexOf(imageFolder)].images.add(imageItem)
111 | }
112 | }
113 | //防止没有图片报异常
114 | if (data.count > 0 && allImages.size > 0) {
115 | //构造所有图片的集合
116 | val allImagesFolder = ImageFolder(activity.resources.getString(R.string.ip_all_images), "/")
117 | allImagesFolder.cover = allImages[0]
118 | allImagesFolder.images = allImages
119 | imageFolders.add(0, allImagesFolder) //确保第一条是所有图片
120 | }
121 | }
122 |
123 | //回调接口,通知图片数据准备完成
124 | loadedListener?.onImagesLoaded(imageFolders)
125 | }
126 |
127 | override fun onLoaderReset(loader: Loader) {
128 | println("--------")
129 |
130 | }
131 |
132 | fun destroyLoader() {
133 | if (currentMode != null) {
134 | activity.supportLoaderManager.destroyLoader(currentMode!!)
135 | }
136 | }
137 |
138 | /**
139 | * 所有图片加载完成的回调接口
140 | */
141 | interface OnImagesLoadedListener {
142 | fun onImagesLoaded(imageFolders: List)
143 | }
144 |
145 | companion object {
146 |
147 | val LOADER_ALL = 0 //加载所有图片
148 | val LOADER_CATEGORY = 1 //分类加载图片
149 | }
150 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/ImagePicker.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library
2 |
3 | import android.content.Context
4 | import com.huburt.library.ImagePicker.pickHelper
5 | import com.huburt.library.bean.ImageItem
6 | import com.huburt.library.loader.ImageLoader
7 | import com.huburt.library.ui.ShadowActivity
8 | import com.huburt.library.view.CropImageView
9 | import kotlin.properties.ReadWriteProperty
10 | import kotlin.reflect.KProperty
11 |
12 | /**
13 | * Created by hubert
14 | *
15 | * Created on 2017/10/12.
16 | */
17 | object ImagePicker {
18 | init {
19 | println("imagePicker init ...")
20 | }
21 |
22 | internal var imageLoader: ImageLoader by InitializationCheck("imageLoader is not initialized, please call 'ImagePicker.init(XX)' in your application's onCreate")
23 |
24 | internal var pickHelper: PickHelper = PickHelper()
25 |
26 | private var customPickHelper: PickHelper? = null
27 |
28 | internal var listener: ImagePicker.OnPickImageResultListener? = null
29 |
30 | /**
31 | * 在Application中初始化图片加载框架
32 | */
33 | @JvmStatic
34 | fun init(imageLoader: ImageLoader) {
35 | this.imageLoader = imageLoader
36 | }
37 |
38 | /**
39 | * 图片选择参数恢复默认,如有自定义默认(saveAsDefault方法保存)优先回复自定义默认
40 | */
41 | @JvmStatic
42 | fun defaultConfig(): ImagePicker {
43 | pickHelper = customPickHelper?.copy() ?: PickHelper()
44 | return this
45 | }
46 |
47 | /**
48 | * 当编辑过参数保存为自定义默认
49 | */
50 | @JvmStatic
51 | fun saveAsDefault(): ImagePicker {
52 | customPickHelper = pickHelper
53 | return this
54 | }
55 |
56 | /**
57 | * 清楚缓存的已选择图片
58 | */
59 | @JvmStatic
60 | fun clear() {
61 | pickHelper.selectedImages.clear()
62 | pickHelper.historyImages.clear()
63 | }
64 |
65 | /**
66 | * 图片数量限制,默认9张
67 | */
68 | @JvmStatic
69 | fun limit(max: Int): ImagePicker {
70 | pickHelper.limit = max
71 | return this
72 | }
73 |
74 | /**
75 | * 是否显示相机,默认显示
76 | */
77 | @JvmStatic
78 | fun showCamera(boolean: Boolean): ImagePicker {
79 | pickHelper.isShowCamera = boolean
80 | return this
81 | }
82 |
83 | /**
84 | * 是否多选,默认显示
85 | */
86 | @JvmStatic
87 | fun multiMode(boolean: Boolean): ImagePicker {
88 | pickHelper.isMultiMode = boolean
89 | return this
90 | }
91 |
92 | /**
93 | * 是否裁剪
94 | */
95 | @JvmStatic
96 | fun isCrop(boolean: Boolean): ImagePicker {
97 | pickHelper.isCrop = boolean
98 | return this
99 | }
100 |
101 | /**
102 | * @param focusStyle 裁剪框的形状
103 | * @param focusWidth 焦点框的宽度
104 | * @param focusHeight 焦点框的高度
105 | * @param outPutX 裁剪保存宽度
106 | * @param outPutY 裁剪保存高度
107 | * @param isSaveRectangle 裁剪后的图片是否是矩形,否者跟随裁剪框的形状
108 | */
109 | @JvmStatic
110 | fun CropConfig(focusStyle: CropImageView.Style, focusWidth: Int, focusHeight: Int, outPutX: Int, outPutY: Int, isSaveRectangle: Boolean) {
111 | pickHelper.focusStyle = focusStyle
112 | pickHelper.focusWidth = focusWidth
113 | pickHelper.focusHeight = focusHeight
114 | pickHelper.outPutX = outPutX
115 | pickHelper.outPutY = outPutY
116 | pickHelper.isSaveRectangle = isSaveRectangle
117 | }
118 |
119 | @JvmStatic
120 | fun pick(context: Context, listener: OnPickImageResultListener) {
121 | this.listener = listener
122 | ShadowActivity.start(context, 0, 0)
123 | }
124 |
125 | @JvmStatic
126 | fun camera(context: Context, listener: OnPickImageResultListener) {
127 | this.listener = listener
128 | ShadowActivity.start(context, 2, 0)
129 | }
130 |
131 | @JvmStatic
132 | fun review(context: Context, position: Int, listener: OnPickImageResultListener) {
133 | this.listener = listener
134 | ShadowActivity.start(context, 1, position)
135 | }
136 |
137 | interface OnPickImageResultListener {
138 | fun onImageResult(imageItems: ArrayList)
139 | }
140 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/ImagePickerProvider.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library
2 |
3 | import android.support.v4.content.FileProvider
4 |
5 | /**
6 | * 自定义一个Provider,以免和引入的项目的provider冲突
7 | *
8 | * Author: nanchen
9 | * Email: liushilin520@foxmail.com
10 | * Date: 2017-03-17 16:10
11 | */
12 |
13 | class ImagePickerProvider : FileProvider()
14 |
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/PickHelper.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library
2 |
3 | import com.huburt.library.bean.ImageItem
4 | import com.huburt.library.view.CropImageView
5 | import java.io.Serializable
6 | import java.util.*
7 |
8 | /**
9 | * Created by hubert
10 | *
11 | * Created on 2017/10/13.
12 | */
13 | data class PickHelper(
14 | var limit: Int = 9, //选择照片限制
15 | var isCrop: Boolean = false, //是否裁剪
16 | var isShowCamera: Boolean = true, //是否显示拍照按钮
17 | var isMultiMode: Boolean = true //选择模式
18 | ) : Serializable {
19 |
20 | var focusStyle = CropImageView.Style.RECTANGLE //裁剪框的形状
21 | var outPutX = 800 //裁剪保存宽度
22 | var outPutY = 800 //裁剪保存高度
23 | var focusWidth = 280 //焦点框的宽度
24 | var focusHeight = 280 //焦点框的高度
25 | var isSaveRectangle = false //裁剪后的图片是否是矩形,否者跟随裁剪框的形状
26 |
27 | val selectedImages: ArrayList = ArrayList()//已经选中的图片数据
28 | val historyImages: ArrayList = ArrayList()//进入时已选中的的图片数据
29 |
30 | fun canSelect(): Boolean = selectedImages.size < limit
31 |
32 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/adapter/ImageFolderAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.adapter
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.BaseAdapter
9 | import android.widget.ImageView
10 | import android.widget.TextView
11 | import com.huburt.library.ImagePicker
12 | import com.huburt.library.R
13 | import com.huburt.library.bean.ImageFolder
14 | import com.huburt.library.util.Utils
15 | import java.util.*
16 |
17 | class ImageFolderAdapter(private val mActivity: Activity, folders: MutableList?) : BaseAdapter() {
18 | private val mInflater: LayoutInflater
19 | private val mImageSize: Int
20 | private var imageFolders: MutableList? = null
21 | var selectIndex = 0
22 | set(i) {
23 | if (selectIndex != i) {
24 | field = i
25 | notifyDataSetChanged()
26 | }
27 | }
28 |
29 | init {
30 | imageFolders = if (folders != null && folders.size > 0) folders else ArrayList()
31 | mImageSize = Utils.getImageItemWidth(mActivity)
32 | mInflater = mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
33 | }
34 |
35 | fun refreshData(folders: MutableList?) {
36 | if (folders != null && folders.size > 0) imageFolders = folders
37 | else imageFolders?.clear()
38 | notifyDataSetChanged()
39 | }
40 |
41 | override fun getCount(): Int = imageFolders?.size ?: 0
42 |
43 | override fun getItem(position: Int): ImageFolder = imageFolders!![position]
44 |
45 | override fun getItemId(position: Int): Long = position.toLong()
46 |
47 | override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
48 | var view = convertView
49 | val holder: ViewHolder
50 | if (view == null) {
51 | view = mInflater.inflate(R.layout.adapter_folder_list_item, parent, false)
52 | holder = ViewHolder(view)
53 | } else {
54 | holder = view.tag as ViewHolder
55 | }
56 |
57 | val (name, _, cover, images) = getItem(position)
58 | holder.folderName.text = name
59 | holder.imageCount.text = mActivity.getString(R.string.ip_folder_image_count, images.size)
60 | if (cover?.path != null) {
61 | ImagePicker.imageLoader?.displayImage(mActivity, cover.path!!, holder.cover, mImageSize, mImageSize)
62 | }
63 |
64 | if (selectIndex == position) {
65 | holder.folderCheck.visibility = View.VISIBLE
66 | } else {
67 | holder.folderCheck.visibility = View.INVISIBLE
68 | }
69 |
70 | return view!!
71 | }
72 |
73 | private inner class ViewHolder(view: View) {
74 | internal var cover: ImageView = view.findViewById(R.id.iv_cover) as ImageView
75 | internal var folderName: TextView = view.findViewById(R.id.tv_folder_name) as TextView
76 | internal var imageCount: TextView = view.findViewById(R.id.tv_image_count) as TextView
77 | internal var folderCheck: ImageView = view.findViewById(R.id.iv_folder_check) as ImageView
78 |
79 | init {
80 | view.tag = this
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/adapter/ImagePageAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.adapter
2 |
3 | import android.app.Activity
4 | import android.support.v4.view.PagerAdapter
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import com.huburt.library.ImagePicker
8 | import com.huburt.library.bean.ImageItem
9 | import com.huburt.library.util.Utils
10 | import uk.co.senab.photoview.PhotoView
11 | import uk.co.senab.photoview.PhotoViewAttacher
12 | import java.util.*
13 |
14 | class ImagePageAdapter(
15 | private val mActivity: Activity,
16 | private var images: ArrayList = ArrayList()
17 | ) : PagerAdapter() {
18 |
19 | private val screenWidth: Int
20 | private val screenHeight: Int
21 | var listener: PhotoViewAttacher.OnPhotoTapListener? = null
22 |
23 | init {
24 | val dm = Utils.getScreenPix(mActivity)
25 | screenWidth = dm.widthPixels
26 | screenHeight = dm.heightPixels
27 | }
28 |
29 | fun setData(images: ArrayList) {
30 | this.images = images
31 | notifyDataSetChanged()
32 | }
33 |
34 | override fun instantiateItem(container: ViewGroup, position: Int): Any {
35 | val photoView = PhotoView(mActivity)
36 | val imageItem = images[position]
37 | ImagePicker.imageLoader?.displayImagePreview(mActivity, imageItem.path!!, photoView, screenWidth, screenHeight)
38 | photoView.setOnPhotoTapListener(listener)
39 | container.addView(photoView)
40 | return photoView
41 | }
42 |
43 | override fun getCount(): Int = images.size
44 |
45 | override fun isViewFromObject(view: View, `object`: Any): Boolean = view === `object`
46 |
47 | override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
48 | container.removeView(`object` as View)
49 | }
50 |
51 | override fun getItemPosition(`object`: Any?): Int = PagerAdapter.POSITION_NONE
52 |
53 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/adapter/ImageRecyclerAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.adapter
2 |
3 | import android.app.Activity
4 | import android.support.v7.widget.AppCompatCheckBox
5 | import android.support.v7.widget.RecyclerView
6 | import android.support.v7.widget.RecyclerView.ViewHolder
7 | import android.text.TextUtils
8 | import android.view.LayoutInflater
9 | import android.view.View
10 | import android.view.ViewGroup
11 | import android.widget.AbsListView
12 | import android.widget.ImageView
13 | import android.widget.Toast
14 | import com.huburt.library.ImagePicker
15 | import com.huburt.library.PickHelper
16 | import com.huburt.library.R
17 | import com.huburt.library.bean.ImageItem
18 | import com.huburt.library.util.Utils
19 | import java.util.*
20 |
21 |
22 | class ImageRecyclerAdapter(
23 | private val mActivity: Activity,
24 | private val pickHelper: PickHelper,
25 | var images: ArrayList = ArrayList()
26 | ) : RecyclerView.Adapter() {
27 |
28 | private val mImageSize: Int = Utils.getImageItemWidth(mActivity) //每个条目的大小
29 | private val mInflater: LayoutInflater = LayoutInflater.from(mActivity)
30 | internal var listener: OnImageItemClickListener? = null //图片被点击的监听
31 |
32 | interface OnImageItemClickListener {
33 | fun onImageItemClick(imageItem: ImageItem, position: Int)
34 | fun onCheckChanged(selected: Int, limit: Int)
35 | fun onCameraClick()
36 | }
37 |
38 | fun refreshData(images: ArrayList?) {
39 | if (images != null) this.images = images
40 | notifyDataSetChanged()
41 | }
42 |
43 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
44 | return if (viewType == ITEM_TYPE_CAMERA) {
45 | CameraViewHolder(mInflater.inflate(R.layout.adapter_camera_item, parent, false))
46 | } else ImageViewHolder(mInflater.inflate(R.layout.adapter_image_list_item, parent, false))
47 | }
48 |
49 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
50 | (holder as? CameraViewHolder)?.bindCamera() ?: (holder as? ImageViewHolder)?.bind(position)
51 | }
52 |
53 | override fun getItemViewType(position: Int): Int =
54 | if (pickHelper.isShowCamera) if (position == 0) ITEM_TYPE_CAMERA else ITEM_TYPE_NORMAL
55 | else ITEM_TYPE_NORMAL
56 |
57 | override fun getItemId(position: Int): Long = position.toLong()
58 |
59 | override fun getItemCount(): Int = if (pickHelper.isShowCamera) images.size + 1 else images.size
60 |
61 | fun getItem(position: Int): ImageItem? =
62 | if (pickHelper.isShowCamera)
63 | if (position == 0) null else images[position - 1]
64 | else images[position]
65 |
66 |
67 | private inner class ImageViewHolder internal constructor(internal var rootView: View) : ViewHolder(rootView) {
68 |
69 | internal var ivThumb: ImageView = rootView.findViewById(R.id.iv_thumb) as ImageView
70 | internal var mask: View = rootView.findViewById(R.id.mask)
71 | internal var checkView: View = rootView.findViewById(R.id.checkView)
72 | internal var cbCheck: AppCompatCheckBox = rootView.findViewById(R.id.cb_check) as AppCompatCheckBox
73 |
74 | init {
75 | rootView.layoutParams = AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mImageSize) //让图片是个正方形
76 | }
77 |
78 | internal fun bind(position: Int) {
79 | val imageItem = getItem(position)
80 | ivThumb.setOnClickListener { listener?.onImageItemClick(imageItem!!, if (pickHelper.isShowCamera) position - 1 else position) }
81 | checkView.setOnClickListener {
82 | if (cbCheck.isChecked) {
83 | pickHelper.selectedImages.remove(imageItem)
84 | mask.visibility = View.GONE
85 | cbCheck.isChecked = false
86 | } else {
87 | if (pickHelper.selectedImages.size >= pickHelper.limit) {
88 | Toast.makeText(mActivity.applicationContext, mActivity.getString(R.string.ip_select_limit, pickHelper.limit), Toast.LENGTH_SHORT).show()
89 | } else {
90 | mask.visibility = View.VISIBLE
91 | pickHelper.selectedImages.add(imageItem!!)
92 | cbCheck.isChecked = true
93 | }
94 | }
95 | listener?.onCheckChanged(pickHelper.selectedImages.size, pickHelper.limit)
96 | }
97 | //根据是否多选,显示或隐藏checkbox
98 | if (pickHelper.isMultiMode) {
99 | cbCheck.visibility = View.VISIBLE
100 | if (contains(pickHelper.selectedImages, imageItem)) {
101 | mask.visibility = View.VISIBLE
102 | cbCheck.isChecked = true
103 | } else {
104 | mask.visibility = View.GONE
105 | cbCheck.isChecked = false
106 | }
107 | } else {
108 | cbCheck.visibility = View.GONE
109 | }
110 | if (imageItem?.path != null) {
111 | ImagePicker.imageLoader.displayImage(mActivity, imageItem.path!!, ivThumb, mImageSize, mImageSize) //显示图片
112 | }
113 | }
114 |
115 | private fun contains(selectedImages: ArrayList, imageItem: ImageItem?): Boolean {
116 | // for (item in selectedImages) {
117 | // if (TextUtils.equals(item.path, imageItem.path)) {
118 | // return true
119 | // }
120 | // }
121 | // return false
122 | //等同于上方
123 | return selectedImages.any { TextUtils.equals(it.path, imageItem?.path) }
124 | }
125 | }
126 |
127 | private inner class CameraViewHolder internal constructor(internal var mItemView: View) : ViewHolder(mItemView) {
128 |
129 | internal fun bindCamera() {
130 | mItemView.layoutParams = AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mImageSize) //让图片是个正方形
131 | mItemView.tag = null
132 | mItemView.setOnClickListener {
133 | listener?.onCameraClick()
134 | }
135 | }
136 | }
137 |
138 | companion object {
139 |
140 | private val ITEM_TYPE_CAMERA = 0 //第一个条目是相机
141 | private val ITEM_TYPE_NORMAL = 1 //第一个条目不是相机
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/adapter/SmallPreviewAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.adapter
2 |
3 | import android.app.Activity
4 | import android.support.v7.widget.RecyclerView
5 | import android.text.TextUtils
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.ImageView
10 | import com.huburt.library.ImagePicker
11 | import com.huburt.library.R
12 | import com.huburt.library.bean.ImageItem
13 | import java.util.*
14 |
15 | /**
16 | * Created by hubert
17 | *
18 | * Created on 2017/11/2.
19 | */
20 | class SmallPreviewAdapter(
21 | private val mActivity: Activity,
22 | var images: List = ArrayList()
23 | ) : RecyclerView.Adapter() {
24 |
25 | var current: ImageItem? = null
26 | set(value) {
27 | field = value
28 | notifyDataSetChanged()
29 | }
30 |
31 | var listener: OnItemClickListener? = null
32 |
33 | override fun getItemCount(): Int = images.size
34 |
35 | override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): SmallPreviewViewHolder {
36 | return SmallPreviewViewHolder(LayoutInflater.from(parent?.context).inflate(R.layout.item_small_preview, parent, false))
37 | }
38 |
39 | override fun onBindViewHolder(holder: SmallPreviewViewHolder?, position: Int) {
40 | holder?.bind(position)
41 | }
42 |
43 | override fun getItemId(position: Int): Long = position.toLong()
44 |
45 | inner class SmallPreviewViewHolder(private var mView: View) : RecyclerView.ViewHolder(mView) {
46 |
47 | val iv_small = mView.findViewById(R.id.iv_small) as ImageView
48 | val v_frame = mView.findViewById(R.id.v_frame)
49 |
50 | fun bind(position: Int) {
51 | mView.setOnClickListener {
52 | listener?.onItemClick(position, images[position])
53 | }
54 | if (TextUtils.equals(current?.path, images[position].path)) {
55 | v_frame.visibility = View.VISIBLE
56 | } else {
57 | v_frame.visibility = View.GONE
58 | }
59 | ImagePicker.imageLoader.displayImage(mActivity, images[position].path!!, iv_small, iv_small.width, iv_small.height)
60 |
61 | }
62 | }
63 |
64 | interface OnItemClickListener {
65 | fun onItemClick(position: Int, imageItem: ImageItem)
66 | }
67 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/bean/ImageFolder.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.bean
2 |
3 | import java.io.Serializable
4 | import java.util.ArrayList
5 |
6 |
7 | data class ImageFolder(var name: String?, //当前文件夹的名字
8 | var path: String?, //当前文件夹的路径
9 | var cover: ImageItem? = null, //当前文件夹需要要显示的缩略图,默认为最近的一次图片
10 | var images: ArrayList = ArrayList() //当前文件夹下所有图片的集合
11 | ) : Serializable {
12 |
13 |
14 | /** 只要文件夹的路径和名字相同,就认为是相同的文件夹 */
15 | override fun equals(other: Any?): Boolean {
16 | try {
17 | val item = other as ImageFolder?
18 | return this.path!!.equals(item!!.path!!, ignoreCase = true) && this.name!!.equals(item.name!!, ignoreCase = true)
19 | } catch (e: ClassCastException) {
20 | e.printStackTrace()
21 | }
22 | return super.equals(other)
23 | }
24 |
25 | override fun hashCode(): Int {
26 | return path?.hashCode() ?: 0
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/bean/ImageItem.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.bean
2 |
3 | import android.os.Parcel
4 | import android.os.Parcelable
5 |
6 | import java.io.Serializable
7 |
8 | data class ImageItem(
9 | var path: String? = null//图片的路径
10 | ) : Serializable, Parcelable {
11 |
12 | var name: String? = null //图片的名字
13 | var size: Long = 0 //图片的大小
14 | var width: Int = 0 //图片的宽度
15 | var height: Int = 0 //图片的高度
16 | var mimeType: String? = null //图片的类型
17 | var addTime: Long = 0 //图片的创建时间
18 |
19 | constructor(parcel: Parcel) : this(parcel.readString()) {
20 | name = parcel.readString()
21 | size = parcel.readLong()
22 | width = parcel.readInt()
23 | height = parcel.readInt()
24 | mimeType = parcel.readString()
25 | addTime = parcel.readLong()
26 | }
27 |
28 | /**
29 | * 图片的路径和创建时间相同就认为是同一张图片
30 | */
31 | override fun equals(other: Any?): Boolean {
32 | if (other is ImageItem) {
33 | val item = other as ImageItem?
34 | return this.path.equals(item!!.path, ignoreCase = true) && this.addTime == item.addTime
35 | }
36 | return super.equals(other)
37 | }
38 |
39 | override fun hashCode(): Int {
40 | return path?.hashCode() ?: 0
41 | }
42 |
43 | override fun writeToParcel(parcel: Parcel, flags: Int) {
44 | parcel.writeString(path)
45 | parcel.writeString(name)
46 | parcel.writeLong(size)
47 | parcel.writeInt(width)
48 | parcel.writeInt(height)
49 | parcel.writeString(mimeType)
50 | parcel.writeLong(addTime)
51 | }
52 |
53 | override fun describeContents(): Int {
54 | return 0
55 | }
56 |
57 | companion object CREATOR : Parcelable.Creator {
58 | override fun createFromParcel(parcel: Parcel): ImageItem {
59 | return ImageItem(parcel)
60 | }
61 |
62 | override fun newArray(size: Int): Array {
63 | return arrayOfNulls(size)
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/loader/ImageLoader.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.loader
2 |
3 | import android.app.Activity
4 | import android.widget.ImageView
5 |
6 | import java.io.Serializable
7 |
8 | interface ImageLoader : Serializable {
9 |
10 | fun displayImage(activity: Activity, path: String, imageView: ImageView, width: Int, height: Int)
11 |
12 | fun displayImagePreview(activity: Activity, path: String, imageView: ImageView, width: Int, height: Int)
13 |
14 | fun clearMemoryCache()
15 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/ui/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.ui
2 |
3 | import android.support.v7.app.AppCompatActivity
4 | import android.widget.Toast
5 |
6 | /**
7 | * Created by hubert
8 | *
9 | * Created on 2017/10/12.
10 | */
11 | open class BaseActivity : AppCompatActivity() {
12 | fun showToast(charSequence: CharSequence) {
13 | Toast.makeText(this, charSequence, Toast.LENGTH_SHORT).show()
14 | }
15 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/ui/ImageCropActivity.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.ui
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.graphics.Bitmap
7 | import android.graphics.BitmapFactory
8 | import android.os.Bundle
9 | import android.view.View
10 | import com.huburt.library.C
11 | import com.huburt.library.ImagePicker
12 | import com.huburt.library.R
13 | import com.huburt.library.bean.ImageItem
14 | import com.huburt.library.util.BitmapUtil
15 | import com.huburt.library.util.FileUtil
16 | import com.huburt.library.util.Utils
17 | import com.huburt.library.view.CropImageView
18 | import kotlinx.android.synthetic.main.activity_image_crop.*
19 | import kotlinx.android.synthetic.main.include_top_bar.*
20 | import java.io.File
21 |
22 | /**
23 | * Created by hubert
24 | *
25 | * Created on 2017/10/20.
26 | */
27 | class ImageCropActivity : BaseActivity(), View.OnClickListener, CropImageView.OnBitmapSaveCompleteListener {
28 |
29 | companion object {
30 | fun start(activity: Activity, requestCode: Int) {
31 | activity.startActivityForResult(Intent(activity, ImageCropActivity::class.java), requestCode)
32 | }
33 | }
34 |
35 | private val pickHelper = ImagePicker.pickHelper
36 | private var mBitmap: Bitmap? = null
37 |
38 | override fun onCreate(savedInstanceState: Bundle?) {
39 | super.onCreate(savedInstanceState)
40 | setContentView(R.layout.activity_image_crop)
41 | initView()
42 | }
43 |
44 | private fun initView() {
45 | btn_back.setOnClickListener(this)
46 | tv_des.text = getString(R.string.ip_photo_crop)
47 | btn_ok.text = getString(R.string.ip_complete)
48 | btn_ok.setOnClickListener(this)
49 | cv_crop_image.setOnBitmapSaveCompleteListener(this)
50 | cv_crop_image.focusStyle = pickHelper.focusStyle
51 | cv_crop_image.focusWidth = Utils.dp2px(this, pickHelper.focusWidth.toFloat())
52 | cv_crop_image.focusHeight = Utils.dp2px(this, pickHelper.focusHeight.toFloat())
53 |
54 | var imagePath = pickHelper.selectedImages[0].path
55 | //缩放图片
56 | val options = BitmapFactory.Options()
57 | options.inJustDecodeBounds = true
58 | BitmapFactory.decodeFile(imagePath, options)
59 | val displayMetrics = resources.displayMetrics
60 | options.inSampleSize = calculateInSampleSize(options, displayMetrics.widthPixels, displayMetrics.heightPixels)
61 | options.inJustDecodeBounds = false
62 | mBitmap = BitmapFactory.decodeFile(imagePath, options)
63 |
64 | cv_crop_image.setImageBitmap(cv_crop_image.rotate(mBitmap, BitmapUtil.getBitmapDegree(imagePath)))
65 | }
66 |
67 | private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
68 | val width = options.outWidth
69 | val height = options.outHeight
70 | var inSampleSize = 1
71 | if (height > reqHeight || width > reqWidth) {
72 | if (width > height) {
73 | inSampleSize = width / reqWidth
74 | } else {
75 | inSampleSize = height / reqHeight
76 | }
77 | }
78 | return inSampleSize
79 | }
80 |
81 | override fun onClick(v: View?) {
82 | when (v?.id) {
83 | R.id.btn_back ->
84 | finish()
85 | R.id.btn_ok ->
86 | cv_crop_image.saveBitmapToFile(FileUtil.getCropCacheFolder(this), pickHelper.outPutX, pickHelper.outPutY, pickHelper.isSaveRectangle)
87 | }
88 | }
89 |
90 | override fun onBitmapSaveSuccess(file: File?) {
91 | pickHelper.selectedImages.removeAt(0)
92 | val imageItem = ImageItem(file?.absolutePath)
93 | pickHelper.selectedImages.add(imageItem)
94 |
95 | val intent = Intent()
96 | intent.putExtra(C.EXTRA_IMAGE_ITEMS, pickHelper.selectedImages)
97 | setResult(Activity.RESULT_OK, intent) //单选不需要裁剪,返回数据
98 | finish()
99 | }
100 |
101 | override fun onBitmapSaveError(file: File?) {
102 |
103 | }
104 |
105 | override fun onDestroy() {
106 | super.onDestroy()
107 | cv_crop_image.setOnBitmapSaveCompleteListener(null)
108 | if (null != mBitmap && !mBitmap!!.isRecycled) {
109 | mBitmap!!.recycle()
110 | mBitmap = null
111 | }
112 | }
113 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/ui/ImageGridActivity.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.ui
2 |
3 | import android.Manifest
4 | import android.app.Activity
5 | import android.content.Intent
6 | import android.content.pm.PackageManager
7 | import android.net.Uri
8 | import android.os.Bundle
9 | import android.support.v4.app.ActivityCompat
10 | import android.support.v7.widget.GridLayoutManager
11 | import android.support.v7.widget.RecyclerView
12 | import android.util.Log
13 | import android.view.Gravity
14 | import android.view.View
15 | import android.view.ViewGroup
16 | import android.view.animation.AnimationUtils
17 | import android.widget.AdapterView
18 | import android.widget.Button
19 | import android.widget.TextView
20 | import com.huburt.library.*
21 | import com.huburt.library.adapter.ImageFolderAdapter
22 | import com.huburt.library.adapter.ImageRecyclerAdapter
23 | import com.huburt.library.bean.ImageFolder
24 | import com.huburt.library.bean.ImageItem
25 | import com.huburt.library.util.CameraUtil
26 | import com.huburt.library.util.Utils
27 | import com.huburt.library.util.isSameDate
28 | import com.huburt.library.view.FolderPopUpWindow
29 | import com.huburt.library.view.GridSpacingItemDecoration
30 | import kotlinx.android.synthetic.main.activity_image_grid.*
31 | import kotlinx.android.synthetic.main.include_top_bar.*
32 | import java.io.File
33 | import java.text.SimpleDateFormat
34 | import java.util.*
35 |
36 | /**
37 | * Created by hubert
38 | *
39 | * Created on 2017/10/12.
40 | */
41 | class ImageGridActivity : BaseActivity(), View.OnClickListener, ImageDataSource.OnImagesLoadedListener, ImageRecyclerAdapter.OnImageItemClickListener {
42 | companion object {
43 |
44 | val REQUEST_PERMISSION_STORAGE = 0x12
45 | val REQUEST_PERMISSION_CAMERA = 0x13
46 | val REQUEST_CAMERA = 0x23
47 | val REQUEST_PREVIEW = 0x9
48 | val REQUEST_CROP = 0x10
49 | val INTENT_MAX = 1000
50 |
51 | /**
52 | * @param takePhoto 是否直接开启拍照
53 | */
54 | fun startForResult(activity: Activity, requestCode: Int, takePhoto: Boolean) {
55 | val intent = Intent(activity, ImageGridActivity::class.java)
56 | intent.putExtra(C.EXTRA_TAKE_PHOTO, takePhoto)
57 | activity.startActivityForResult(intent, requestCode)
58 | }
59 | }
60 |
61 | private val pickerHelper: PickHelper = ImagePicker.pickHelper
62 | private val imageDataSource = ImageDataSource(this)
63 | private lateinit var adapter: ImageRecyclerAdapter
64 | private lateinit var mFolderPopupWindow: FolderPopUpWindow
65 | private lateinit var mImageFolderAdapter: ImageFolderAdapter
66 | private lateinit var imageFolders: List
67 | private lateinit var takeImageFile: File
68 | private var takePhoto: Boolean = false
69 |
70 | override fun onCreate(savedInstanceState: Bundle?) {
71 | super.onCreate(savedInstanceState)
72 | setContentView(R.layout.activity_image_grid)
73 | //是否直接打开相机
74 | takePhoto = intent.extras[C.EXTRA_TAKE_PHOTO] as Boolean
75 | if (takePhoto) {
76 | onCameraClick()
77 | }
78 |
79 | initView()
80 | initPopWindow()
81 | loadData()
82 | }
83 |
84 | private fun loadData() {
85 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
86 | != PackageManager.PERMISSION_GRANTED) {
87 | ActivityCompat.requestPermissions(this,
88 | arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_PERMISSION_STORAGE)
89 | } else {
90 | imageDataSource.loadImage(this)
91 | }
92 | }
93 |
94 | override fun onResume() {
95 | super.onResume()
96 | //数据刷新
97 | adapter.notifyDataSetChanged()
98 | onCheckChanged(pickerHelper.selectedImages.size, pickerHelper.limit)
99 | }
100 |
101 | private fun initView() {
102 | ll_dir.setOnClickListener(this)
103 | btn_ok.setOnClickListener(this)
104 | btn_back.setOnClickListener(this)
105 | btn_preview.setOnClickListener(this)
106 |
107 | recycler.layoutManager = GridLayoutManager(this, 3)
108 | recycler.addItemDecoration(GridSpacingItemDecoration(3, Utils.dp2px(this, 2f), false))
109 | adapter = ImageRecyclerAdapter(this, pickerHelper)
110 | adapter.listener = this
111 | recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
112 | override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
113 | if (newState == RecyclerView.SCROLL_STATE_IDLE) {
114 | if (tv_date.visibility == View.VISIBLE) {
115 | tv_date.animation = AnimationUtils.loadAnimation(this@ImageGridActivity, R.anim.fade_out)
116 | tv_date.visibility = View.GONE
117 | }
118 | } else {
119 | if (tv_date.visibility == View.GONE) {
120 | tv_date.animation = AnimationUtils.loadAnimation(this@ImageGridActivity, R.anim.fade_in)
121 | tv_date.visibility = View.VISIBLE
122 | }
123 | val gridLayoutManager = recycler.layoutManager as GridLayoutManager
124 | val position = gridLayoutManager.findFirstCompletelyVisibleItemPosition()
125 | val addTime = adapter.getItem(position)?.addTime
126 | Log.d("hubert", "图片,position:$position ,addTime: $addTime")
127 | if (addTime != null) {
128 | val calendar = Calendar.getInstance()
129 | calendar.timeInMillis = addTime * 1000
130 | if (isSameDate(calendar.time, Calendar.getInstance().time)) {
131 | tv_date.text = "本周"
132 | } else {
133 | val format = SimpleDateFormat("yyyy/MM", Locale.getDefault())
134 | tv_date.text = format.format(calendar.time)
135 | }
136 | }
137 | }
138 | }
139 | })
140 |
141 | if (pickerHelper.isMultiMode) {
142 | btn_ok.visibility = View.VISIBLE
143 | btn_preview.visibility = View.VISIBLE
144 | } else {
145 | btn_ok.visibility = View.GONE
146 | btn_preview.visibility = View.GONE
147 | }
148 | }
149 |
150 | private fun initPopWindow() {
151 | mImageFolderAdapter = ImageFolderAdapter(this, null)
152 | mFolderPopupWindow = FolderPopUpWindow(this, mImageFolderAdapter)
153 | mFolderPopupWindow.setOnItemClickListener(object : FolderPopUpWindow.OnItemClickListener {
154 | override fun onItemClick(adapterView: AdapterView<*>, view: View, position: Int, l: Long) {
155 | mImageFolderAdapter.selectIndex = position
156 | mFolderPopupWindow.dismiss()
157 | val imageFolder = adapterView.adapter?.getItem(position) as ImageFolder
158 | adapter.refreshData(imageFolder.images)
159 | tv_dir.text = imageFolder.name
160 | }
161 | })
162 | footer_bar.post({ mFolderPopupWindow.setMargin(footer_bar.height) })
163 | }
164 |
165 | private fun showPopupFolderList() {
166 | mImageFolderAdapter.refreshData(imageFolders.toMutableList()) //刷新数据
167 | if (mFolderPopupWindow.isShowing) {
168 | mFolderPopupWindow.dismiss()
169 | } else {
170 | mFolderPopupWindow.showAtLocation(footer_bar, Gravity.NO_GRAVITY, 0, 0)
171 | //默认选择当前选择的上一个,当目录很多时,直接定位到已选中的条目
172 | var index = mImageFolderAdapter.selectIndex
173 | index = if (index == 0) index else index - 1
174 | mFolderPopupWindow.setSelection(index)
175 | }
176 | }
177 |
178 | override fun onImageItemClick(imageItem: ImageItem, position: Int) {
179 | if (pickerHelper.isMultiMode) {
180 | var images = adapter.images
181 | var p = position
182 | if (images.size > INTENT_MAX) {//数据量过大
183 | val s: Int
184 | val e: Int
185 | if (position < images.size / 2) {//点击position在list靠前
186 | s = Math.max(position - INTENT_MAX / 2, 0)
187 | e = Math.min(s + INTENT_MAX, images.size)
188 | } else {
189 | e = Math.min(position + INTENT_MAX / 2, images.size)
190 | s = Math.max(e - INTENT_MAX, 0)
191 | }
192 | p = position - s
193 | Log.e("hubert", "start:$s , end:$e , position:$p")
194 | // images = ArrayList()
195 | // for (i in s until e) {
196 | // images.add(adapter.images[i])
197 | // }
198 | //等同于上面,IDE提示换成的Kotlin的高阶函数
199 | images = (s until e).mapTo(ArrayList()) { adapter.images[it] }
200 | }
201 | ImagePreviewActivity.startForResult(this, REQUEST_PREVIEW, p, images)
202 | } else {
203 | pickerHelper.selectedImages.clear()
204 | pickerHelper.selectedImages.add(imageItem)
205 | if (pickerHelper.isCrop) {//需要裁剪
206 | ImageCropActivity.start(this, REQUEST_CROP)
207 | } else {
208 | setResult()
209 | }
210 | }
211 | }
212 |
213 | override fun onCheckChanged(selected: Int, limit: Int) {
214 | if (selected == 0) {
215 | btn_ok.isEnabled = false
216 | btn_ok.text = getString(R.string.ip_complete)
217 | btn_ok.setTextColor(resources.getColor(R.color.ip_text_secondary_inverted))
218 | btn_preview.isEnabled = false
219 | btn_preview.text = getString(R.string.ip_preview)
220 | btn_preview.setTextColor(resources.getColor(R.color.ip_text_secondary_inverted))
221 | } else {
222 | btn_ok.isEnabled = true
223 | btn_ok.text = getString(R.string.ip_select_complete, selected, limit)
224 | btn_ok.setTextColor(resources.getColor(R.color.ip_text_primary_inverted))
225 | btn_preview.isEnabled = true
226 | btn_preview.text = getString(R.string.ip_preview_count, selected)
227 | btn_preview.setTextColor(resources.getColor(R.color.ip_text_primary_inverted))
228 | }
229 | }
230 |
231 | override fun onCameraClick() {
232 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
233 | ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA),
234 | ImageGridActivity.REQUEST_PERMISSION_CAMERA)
235 | } else {
236 | takeImageFile = CameraUtil.takePicture(this, REQUEST_CAMERA)
237 | }
238 | }
239 |
240 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
241 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
242 | if (requestCode == REQUEST_PERMISSION_STORAGE) {
243 | if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
244 | imageDataSource.loadImage(this)
245 | } else {
246 | showToast("权限被禁止,无法选择本地图片")
247 | }
248 | } else if (requestCode == REQUEST_PERMISSION_CAMERA) {
249 | if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
250 | takeImageFile = CameraUtil.takePicture(this, REQUEST_CAMERA)
251 | } else {
252 | showToast("权限被禁止,无法打开相机")
253 | }
254 | }
255 | }
256 |
257 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
258 | super.onActivityResult(requestCode, resultCode, data)
259 | if (requestCode == REQUEST_CAMERA) {//相机返回
260 | if (resultCode == Activity.RESULT_OK) {
261 | Log.e("hubert", takeImageFile.absolutePath)
262 | //广播通知新增图片
263 | val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
264 | mediaScanIntent.data = Uri.fromFile(takeImageFile)
265 | sendBroadcast(mediaScanIntent)
266 |
267 | val imageItem = ImageItem(takeImageFile.absolutePath)
268 | pickerHelper.selectedImages.clear()
269 | pickerHelper.selectedImages.add(imageItem)
270 |
271 | if (pickerHelper.isCrop) {//需要裁剪
272 | ImageCropActivity.start(this, REQUEST_CROP)
273 | } else {
274 | setResult()
275 | }
276 | } else if (takePhoto) {//直接拍照返回时不再展示列表
277 | finish()
278 | }
279 | } else if (requestCode == REQUEST_PREVIEW) {//预览界面返回
280 | if (resultCode == Activity.RESULT_OK) {
281 | setResult()
282 | }
283 | } else if (requestCode == REQUEST_CROP) {//裁剪结果
284 | if (resultCode == Activity.RESULT_OK) {
285 | setResult()
286 | }
287 | }
288 | }
289 |
290 | private fun setResult() {
291 | val result = Intent()
292 | result.putExtra(C.EXTRA_IMAGE_ITEMS, pickerHelper.selectedImages)
293 | setResult(Activity.RESULT_OK, result)
294 | finish()
295 | }
296 |
297 | override fun onClick(v: View?) {
298 | when (v?.id) {
299 | R.id.ll_dir -> showPopupFolderList()
300 | R.id.btn_ok -> setResult()
301 | R.id.btn_preview -> ImagePreviewActivity.startForResult(this, REQUEST_PREVIEW, 0, pickerHelper.selectedImages)
302 | R.id.btn_back -> {
303 | setResult(Activity.RESULT_CANCELED)
304 | finish()
305 | }
306 | }
307 | }
308 |
309 | override fun onImagesLoaded(imageFolders: List) {
310 | this.imageFolders = imageFolders
311 | adapter.refreshData(imageFolders[0].images)
312 | recycler.adapter = adapter
313 | }
314 |
315 | override fun onDestroy() {
316 | super.onDestroy()
317 | imageDataSource.destroyLoader()
318 | }
319 |
320 | }
321 |
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/ui/ImagePreviewActivity.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.ui
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.support.v4.view.ViewPager
7 | import android.support.v7.widget.LinearLayoutManager
8 | import android.view.View
9 | import android.view.animation.AnimationUtils
10 | import com.huburt.library.C
11 | import com.huburt.library.R
12 | import com.huburt.library.adapter.SmallPreviewAdapter
13 | import com.huburt.library.bean.ImageItem
14 | import kotlinx.android.synthetic.main.activity_image_preview.*
15 | import kotlinx.android.synthetic.main.include_top_bar.*
16 | import uk.co.senab.photoview.PhotoViewAttacher
17 |
18 | /**
19 | * Created by hubert
20 | *
21 | * Created on 2017/10/19.
22 | */
23 | class ImagePreviewActivity : ImagePreviewBaseActivity(), View.OnClickListener, PhotoViewAttacher.OnPhotoTapListener {
24 |
25 | private lateinit var imageItems: ArrayList
26 | private var current: Int = 0
27 | private var previewAdapter: SmallPreviewAdapter = SmallPreviewAdapter(this, pickHelper.selectedImages)
28 |
29 | companion object {
30 |
31 | fun startForResult(activity: Activity, requestCode: Int, position: Int, imageItems: ArrayList) {
32 | val intent = Intent(activity, ImagePreviewActivity::class.java)
33 | intent.putExtra(C.EXTRA_IMAGE_ITEMS, imageItems)
34 | intent.putExtra(C.EXTRA_POSITION, position)
35 | activity.startActivityForResult(intent, requestCode)
36 | }
37 | }
38 |
39 | override fun onCreate(savedInstanceState: Bundle?) {
40 | super.onCreate(savedInstanceState)
41 | imageItems = intent.extras[C.EXTRA_IMAGE_ITEMS] as ArrayList
42 | current = intent.extras[C.EXTRA_POSITION] as Int
43 | init()
44 | }
45 |
46 | private fun init() {
47 | btn_ok.setOnClickListener(this)
48 | bottom_bar.visibility = View.VISIBLE
49 |
50 | tv_des.text = getString(R.string.ip_preview_image_count, current + 1, imageItems.size)
51 | viewpager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
52 | override fun onPageSelected(position: Int) {
53 | current = position
54 | val imageItem = imageItems[position]
55 | val contains = pickHelper.selectedImages.contains(imageItem)
56 | cb_check.isChecked = contains
57 | tv_des.text = getString(R.string.ip_preview_image_count, position + 1, imageItems.size)
58 | updatePreview()
59 | }
60 | })
61 |
62 | imagePageAdapter.setData(imageItems)
63 | viewpager.currentItem = current
64 |
65 | onCheckChanged(pickHelper.selectedImages.size, pickHelper.limit)
66 |
67 | cb_check.isChecked = pickHelper.selectedImages.contains(imageItems[current])
68 | cb_check.setOnClickListener {
69 | //checkBox 点击时会自动处理isCheck的状态转变,也就是说如果是选中状态,点击触发OnclickListener时,isCheck已经变成false了
70 | //cbCheck.isChecked = !cbCheck.isChecked
71 | val imageItem = imageItems[current]
72 | if (cb_check.isChecked) {
73 | if (pickHelper.canSelect()) {
74 | pickHelper.selectedImages.add(imageItem)
75 | } else {
76 | showToast(getString(R.string.ip_select_limit, pickHelper.limit))
77 | cb_check.isChecked = false
78 | }
79 | previewAdapter.notifyItemInserted(pickHelper.selectedImages.size - 1)
80 | } else {
81 | val index = pickHelper.selectedImages.indexOf(imageItem)
82 | pickHelper.selectedImages.remove(imageItem)
83 | previewAdapter.notifyItemRemoved(index)
84 | }
85 | onCheckChanged(pickHelper.selectedImages.size, pickHelper.limit)
86 | updatePreview()
87 | }
88 |
89 | rv_small.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
90 | previewAdapter.listener = object : SmallPreviewAdapter.OnItemClickListener {
91 | override fun onItemClick(position: Int, imageItem: ImageItem) {
92 | viewpager.setCurrentItem(imageItems.indexOf(imageItem), false)
93 | }
94 | }
95 | rv_small.adapter = previewAdapter
96 | updatePreview()
97 | }
98 |
99 | private fun updatePreview() {
100 | if (pickHelper.selectedImages.size > 0) {
101 | rv_small.visibility = View.VISIBLE
102 | val index = pickHelper.selectedImages.indexOf(imageItems[current])
103 | previewAdapter.current = if (index >= 0) pickHelper.selectedImages[index] else null
104 | if (index >= 0) {
105 | rv_small.smoothScrollToPosition(index)
106 | }
107 | } else {
108 | rv_small.visibility = View.GONE
109 | }
110 | }
111 |
112 | private fun onCheckChanged(selected: Int, limit: Int) {
113 | if (selected == 0) {
114 | btn_ok.isEnabled = false
115 | btn_ok.text = getString(R.string.ip_complete)
116 | btn_ok.setTextColor(resources.getColor(R.color.ip_text_secondary_inverted))
117 | } else {
118 | btn_ok.isEnabled = true
119 | btn_ok.text = getString(R.string.ip_select_complete, selected, limit)
120 | btn_ok.setTextColor(resources.getColor(R.color.ip_text_primary_inverted))
121 | }
122 | }
123 |
124 | override fun onClick(v: View?) {
125 | when (v?.id) {
126 | R.id.btn_ok -> {
127 | setResult(Activity.RESULT_OK)
128 | finish()
129 | }
130 | }
131 | }
132 |
133 | override fun onOutsidePhotoTap() {
134 | changeTopAndBottomBar()
135 | }
136 |
137 | override fun onPhotoTap(view: View?, x: Float, y: Float) {
138 | changeTopAndBottomBar()
139 | }
140 |
141 | private fun changeTopAndBottomBar() {
142 | if (top_bar.visibility == View.VISIBLE) {
143 | top_bar.animation = AnimationUtils.loadAnimation(this, R.anim.top_out)
144 | bottom_bar.animation = AnimationUtils.loadAnimation(this, R.anim.fade_out)
145 | top_bar.visibility = View.GONE
146 | bottom_bar.visibility = View.GONE
147 | } else {
148 | top_bar.animation = AnimationUtils.loadAnimation(this, R.anim.top_in)
149 | bottom_bar.animation = AnimationUtils.loadAnimation(this, R.anim.fade_in)
150 | top_bar.visibility = View.VISIBLE
151 | bottom_bar.visibility = View.VISIBLE
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/ui/ImagePreviewBaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.ui
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.support.v4.view.ViewPager
7 | import android.support.v7.widget.AppCompatCheckBox
8 | import android.view.View
9 | import android.view.animation.AnimationUtils
10 | import android.widget.Button
11 | import android.widget.TextView
12 | import com.huburt.library.C
13 | import com.huburt.library.ImagePicker
14 | import com.huburt.library.PickHelper
15 | import com.huburt.library.R
16 | import com.huburt.library.adapter.ImagePageAdapter
17 | import com.huburt.library.bean.ImageItem
18 | import kotlinx.android.synthetic.main.activity_image_preview.*
19 | import kotlinx.android.synthetic.main.include_top_bar.*
20 | import uk.co.senab.photoview.PhotoViewAttacher
21 |
22 | /**
23 | * Created by hubert
24 | *
25 | * Created on 2017/10/24.
26 | */
27 | abstract class ImagePreviewBaseActivity : BaseActivity(), PhotoViewAttacher.OnPhotoTapListener {
28 |
29 |
30 | protected lateinit var imagePageAdapter: ImagePageAdapter
31 | protected var pickHelper: PickHelper = ImagePicker.pickHelper
32 |
33 |
34 | override fun onCreate(savedInstanceState: Bundle?) {
35 | super.onCreate(savedInstanceState)
36 | setContentView(R.layout.activity_image_preview)
37 | initView()
38 | }
39 |
40 | private fun initView() {
41 | btn_back.setOnClickListener { finish() }
42 |
43 | imagePageAdapter = ImagePageAdapter(this)
44 | imagePageAdapter.listener = this
45 | viewpager.adapter = imagePageAdapter
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/ui/ImagePreviewDelActivity.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.ui
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.support.v4.view.ViewPager
7 | import android.view.View
8 | import android.view.animation.AnimationUtils
9 | import com.huburt.library.C
10 | import com.huburt.library.R
11 | import kotlinx.android.synthetic.main.activity_image_preview.*
12 | import kotlinx.android.synthetic.main.include_top_bar.*
13 |
14 | /**
15 | * Created by hubert
16 | *
17 | * Created on 2017/10/24.
18 | */
19 | class ImagePreviewDelActivity : ImagePreviewBaseActivity() {
20 |
21 | private var current: Int = 0
22 |
23 | companion object {
24 | fun startForResult(activity: Activity, requestCode: Int, position: Int) {
25 | val intent = Intent(activity, ImagePreviewDelActivity::class.java)
26 | intent.putExtra(C.EXTRA_POSITION, position)
27 | activity.startActivityForResult(intent, requestCode)
28 | }
29 | }
30 |
31 | override fun onCreate(savedInstanceState: Bundle?) {
32 | super.onCreate(savedInstanceState)
33 | current = intent.extras[C.EXTRA_POSITION] as Int
34 | init()
35 | }
36 |
37 | private fun init() {
38 | btn_back.setOnClickListener({
39 | setResult()
40 | })
41 | btn_ok.visibility = View.GONE
42 | btn_del.visibility = View.VISIBLE
43 |
44 | updateTitle()
45 | imagePageAdapter.setData(pickHelper.selectedImages)
46 | viewpager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
47 | override fun onPageSelected(position: Int) {
48 | current = position
49 | updateTitle()
50 | }
51 | })
52 | viewpager.currentItem = current
53 |
54 | btn_del.setOnClickListener({
55 | pickHelper.selectedImages.removeAt(current)
56 | if (current != 0) current -= 1
57 | imagePageAdapter.notifyDataSetChanged()
58 | viewpager.currentItem = current
59 | updateTitle()
60 | if (pickHelper.selectedImages.size == 0) {
61 | setResult()
62 | }
63 | })
64 | }
65 |
66 | private fun setResult() {
67 | val data = Intent()
68 | data.putExtra(C.EXTRA_IMAGE_ITEMS, pickHelper.selectedImages)
69 | setResult(Activity.RESULT_OK, data)
70 | finish()
71 | }
72 |
73 | private fun updateTitle() {
74 | tv_des.text = getString(R.string.ip_preview_image_count,
75 | if (pickHelper.selectedImages.size > 0) current + 1 else current,
76 | pickHelper.selectedImages.size)
77 | }
78 |
79 | override fun onPhotoTap(view: View?, x: Float, y: Float) {
80 | changeTopBar()
81 | }
82 |
83 | override fun onOutsidePhotoTap() {
84 | changeTopBar()
85 | }
86 |
87 | private fun changeTopBar() {
88 | if (top_bar.visibility == View.VISIBLE) {
89 | top_bar.animation = AnimationUtils.loadAnimation(this, R.anim.top_out)
90 | top_bar.visibility = View.GONE
91 | } else {
92 | top_bar.animation = AnimationUtils.loadAnimation(this, R.anim.top_in)
93 | top_bar.visibility = View.VISIBLE
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/ui/ShadowActivity.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.ui
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.os.Bundle
7 | import com.huburt.library.C
8 | import com.huburt.library.ImagePicker
9 | import com.huburt.library.bean.ImageItem
10 |
11 | /**
12 | * Created by hubert
13 | *
14 | * Created on 2017/10/20.
15 | */
16 | class ShadowActivity : BaseActivity() {
17 |
18 | private var type: Int = 0//0pick 1review 2camera
19 | private var position: Int = 0
20 |
21 | companion object {
22 | fun start(context: Context, type: Int, position: Int) {
23 | val intent = Intent(context, ShadowActivity::class.java)
24 | intent.putExtra(C.EXTRA_TYPE, type)
25 | intent.putExtra(C.EXTRA_POSITION, position)
26 | context.startActivity(intent)
27 | }
28 | }
29 |
30 | override fun onCreate(savedInstanceState: Bundle?) {
31 | super.onCreate(savedInstanceState)
32 | type = intent.extras[C.EXTRA_TYPE] as Int
33 | position = intent.extras[C.EXTRA_POSITION] as Int
34 | startPick()
35 | }
36 |
37 | private fun startPick() {
38 | when (type) {
39 | 0 -> ImageGridActivity.startForResult(this, 101, false)
40 | 1 -> ImagePreviewDelActivity.startForResult(this, 102, position)
41 | 2 -> ImageGridActivity.startForResult(this, 101, true)
42 | }
43 | }
44 |
45 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
46 | super.onActivityResult(requestCode, resultCode, data)
47 | val historyImages = ImagePicker.pickHelper.historyImages
48 | if (resultCode == Activity.RESULT_OK && data != null) {
49 | val images = data.extras[C.EXTRA_IMAGE_ITEMS] as ArrayList
50 | historyImages.let {
51 | it.clear()
52 | it.addAll(images)
53 | }
54 | ImagePicker.listener?.onImageResult(images)
55 | } else if (resultCode == Activity.RESULT_CANCELED) {
56 | ImagePicker.pickHelper.selectedImages.let {
57 | it.clear()
58 | it.addAll(historyImages)
59 | }
60 | ImagePicker.listener?.onImageResult(historyImages)
61 | }
62 | ImagePicker.listener = null
63 | finish()
64 | }
65 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/util/BitmapUtil.java:
--------------------------------------------------------------------------------
1 | package com.huburt.library.util;
2 |
3 | import android.app.Activity;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapFactory;
6 | import android.graphics.Matrix;
7 | import android.media.ExifInterface;
8 | import android.net.Uri;
9 | import android.provider.MediaStore;
10 |
11 | import java.io.File;
12 | import java.io.IOException;
13 |
14 | /**
15 | *
16 | * Bitmap工具类,主要是解决拍照旋转的适配
17 | *
18 | * Author: nanchen
19 | * Email: liushilin520@foxmail.com
20 | * Date: 2017-03-20 13:27
21 | */
22 |
23 | public class BitmapUtil {
24 |
25 | private BitmapUtil() {
26 | throw new UnsupportedOperationException("u can't instantiate me...");
27 | }
28 |
29 | /**
30 | * 获取图片的旋转角度
31 | *
32 | * @param path 图片绝对路径
33 | * @return 图片的旋转角度
34 | */
35 | public static int getBitmapDegree(String path) {
36 | int degree = 0;
37 | try {
38 | // 从指定路径下读取图片,并获取其EXIF信息
39 | ExifInterface exifInterface = new ExifInterface(path);
40 | // 获取图片的旋转信息
41 | int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
42 | switch (orientation) {
43 | case ExifInterface.ORIENTATION_ROTATE_90:
44 | degree = 90;
45 | break;
46 | case ExifInterface.ORIENTATION_ROTATE_180:
47 | degree = 180;
48 | break;
49 | case ExifInterface.ORIENTATION_ROTATE_270:
50 | degree = 270;
51 | break;
52 | }
53 | } catch (IOException e) {
54 | e.printStackTrace();
55 | }
56 | return degree;
57 | }
58 |
59 | /**
60 | * 将图片按照指定的角度进行旋转
61 | *
62 | * @param bitmap 需要旋转的图片
63 | * @param degree 指定的旋转角度
64 | * @return 旋转后的图片
65 | */
66 | public static Bitmap rotateBitmapByDegree(Bitmap bitmap, int degree) {
67 | // 根据旋转角度,生成旋转矩阵
68 | Matrix matrix = new Matrix();
69 | matrix.postRotate(degree);
70 | // 将原始图片按照旋转矩阵进行旋转,并得到新的图片
71 | Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
72 | if (!bitmap.isRecycled()) {
73 | bitmap.recycle();
74 | }
75 | return newBitmap;
76 | }
77 |
78 | /**
79 | * 获取我们需要的整理过旋转角度的Uri
80 | * @param activity 上下文环境
81 | * @param path 路径
82 | * @return 正常的Uri
83 | */
84 | public static Uri getRotatedUri(Activity activity, String path){
85 | int degree = BitmapUtil.getBitmapDegree(path);
86 | if (degree != 0){
87 | Bitmap bitmap = BitmapFactory.decodeFile(path);
88 | Bitmap newBitmap = BitmapUtil.rotateBitmapByDegree(bitmap,degree);
89 | return Uri.parse(MediaStore.Images.Media.insertImage(activity.getContentResolver(),newBitmap,null,null));
90 | }else{
91 | return Uri.fromFile(new File(path));
92 | }
93 | }
94 |
95 | /**
96 | * 将图片按照指定的角度进行旋转
97 | *
98 | * @param path 需要旋转的图片的路径
99 | * @param degree 指定的旋转角度
100 | * @return 旋转后的图片
101 | */
102 | public static Bitmap rotateBitmapByDegree(String path, int degree) {
103 | Bitmap bitmap = BitmapFactory.decodeFile(path);
104 | return rotateBitmapByDegree(bitmap,degree);
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/util/CameraUtil.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.util
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.content.pm.PackageManager
6 | import android.net.Uri
7 | import android.os.Build
8 | import android.os.Environment
9 | import android.provider.MediaStore
10 | import android.support.v4.content.FileProvider
11 | import android.util.Log
12 | import java.io.File
13 | import java.text.SimpleDateFormat
14 | import java.util.*
15 |
16 | /**
17 | * Created by hubert
18 | *
19 | * Created on 2017/10/20.
20 | */
21 | object CameraUtil {
22 | fun takePicture(activity: Activity, requestCode: Int): File {
23 | var takeImageFile =
24 | if (Utils.existSDCard())
25 | File(Environment.getExternalStorageDirectory(), "/DCIM/camera/")
26 | else
27 | Environment.getDataDirectory()
28 | takeImageFile = createFile(takeImageFile, "IMG_", ".jpg")
29 | val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
30 | takePictureIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
31 | if (takePictureIntent.resolveActivity(activity.packageManager) != null) {
32 |
33 | // 默认情况下,即不需要指定intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
34 | // 照相机有自己默认的存储路径,拍摄的照片将返回一个缩略图。如果想访问原始图片,
35 | // 可以通过dat extra能够得到原始图片位置。即,如果指定了目标uri,data就没有数据,
36 | // 如果没有指定uri,则data就返回有数据!
37 |
38 | val uri: Uri
39 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
40 | uri = Uri.fromFile(takeImageFile)
41 | } else {
42 | // 7.0 调用系统相机拍照不再允许使用Uri方式,应该替换为FileProvider
43 | // 并且这样可以解决MIUI系统上拍照返回size为0的情况
44 | uri = FileProvider.getUriForFile(activity, ProviderUtil.getFileProviderName(activity), takeImageFile)
45 | //加入uri权限 要不三星手机不能拍照
46 | val resInfoList = activity.packageManager.queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY)
47 | resInfoList
48 | .map { it.activityInfo.packageName }
49 | .forEach { activity.grantUriPermission(it, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION) }
50 | }
51 | Log.e("nanchen", ProviderUtil.getFileProviderName(activity))
52 | takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri)
53 | }
54 | activity.startActivityForResult(takePictureIntent, requestCode)
55 | return takeImageFile
56 | }
57 |
58 | /**
59 | * 根据系统时间、前缀、后缀产生一个文件
60 | */
61 | fun createFile(folder: File, prefix: String, suffix: String): File {
62 | if (!folder.exists() || !folder.isDirectory) folder.mkdirs()
63 | val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.CHINA)
64 | val filename = prefix + dateFormat.format(Date(System.currentTimeMillis())) + suffix
65 | return File(folder, filename)
66 | }
67 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/util/DateUtil.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("DateUtil")
2 |
3 | package com.huburt.library.util
4 |
5 | import java.util.*
6 |
7 | /**
8 | * Created by hubert
9 | *
10 | * Created on 2017/11/2.
11 | */
12 | fun isSameDate(date1: Date, date2: Date): Boolean {
13 | val cal1 = Calendar.getInstance()
14 | val cal2 = Calendar.getInstance()
15 | cal1.firstDayOfWeek = Calendar.MONDAY//西方周日为一周的第一天,咱得将周一设为一周第一天
16 | cal2.firstDayOfWeek = Calendar.MONDAY
17 | cal1.time = date1
18 | cal2.time = date2
19 | val subYear = cal1.get(Calendar.YEAR) - cal2.get(Calendar.YEAR)
20 | if (subYear == 0) {
21 | if (cal1.get(Calendar.WEEK_OF_YEAR) == cal2.get(Calendar.WEEK_OF_YEAR))
22 | return true
23 | } else if (subYear == 1 && cal2.get(Calendar.MONTH) == 11) {
24 | if (cal1.get(Calendar.WEEK_OF_YEAR) == cal2.get(Calendar.WEEK_OF_YEAR))
25 | return true
26 | } else if (subYear == -1 && cal1.get(Calendar.MONTH) == 11) {
27 | if (cal1.get(Calendar.WEEK_OF_YEAR) == cal2.get(Calendar.WEEK_OF_YEAR))
28 | return true
29 | }
30 | return false
31 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/util/FileUtil.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.util
2 |
3 | import android.content.Context
4 | import java.io.File
5 |
6 | /**
7 | * Created by hubert
8 | *
9 | * Created on 2017/10/31.
10 | */
11 | object FileUtil {
12 | fun getCropCacheFolder(context: Context): File {
13 | return File(context.cacheDir.toString() + "/ImagePicker/cropTemp/")
14 | }
15 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/util/ProviderUtil.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.util
2 |
3 | import android.content.Context
4 |
5 | /**
6 | * Created by hubert
7 | *
8 | * Created on 2017/10/12.
9 | */
10 | object ProviderUtil {
11 | fun getFileProviderName(context: Context): String = context.packageName + ".provider"
12 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/util/Utils.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.util
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.os.Build
6 | import android.os.Environment
7 | import android.util.DisplayMetrics
8 | import android.util.TypedValue
9 | import android.view.Display
10 | import android.view.KeyCharacterMap
11 | import android.view.KeyEvent
12 | import android.view.ViewConfiguration
13 | import android.view.WindowManager
14 |
15 |
16 | object Utils {
17 |
18 | /** 获得状态栏的高度 */
19 | @JvmStatic
20 | fun getStatusHeight(context: Context): Int {
21 | var statusHeight = -1
22 | try {
23 | val clazz = Class.forName("com.android.internal.R\$dimen")
24 | val obj = clazz.newInstance()
25 | val height = Integer.parseInt(clazz.getField("status_bar_height").get(obj).toString())
26 | statusHeight = context.resources.getDimensionPixelSize(height)
27 | } catch (e: Exception) {
28 | e.printStackTrace()
29 | }
30 |
31 | return statusHeight
32 | }
33 |
34 | /** 根据屏幕宽度与密度计算GridView显示的列数, 最少为三列,并获取Item宽度 */
35 | @JvmStatic
36 | fun getImageItemWidth(activity: Activity): Int {
37 | val screenWidth = activity.resources.displayMetrics.widthPixels
38 | val densityDpi = activity.resources.displayMetrics.densityDpi
39 | var cols = screenWidth / densityDpi
40 | cols = if (cols < 3) 3 else cols
41 | val columnSpace = (2 * activity.resources.displayMetrics.density).toInt()
42 | return (screenWidth - columnSpace * (cols - 1)) / cols
43 | }
44 |
45 | /**
46 | * 判断SDCard是否可用
47 | */
48 | @JvmStatic
49 | fun existSDCard(): Boolean {
50 | return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
51 | }
52 |
53 | /**
54 | * 获取手机大小(分辨率)
55 | */
56 | @JvmStatic
57 | fun getScreenPix(activity: Activity): DisplayMetrics {
58 | val displaysMetrics = DisplayMetrics()
59 | activity.windowManager.defaultDisplay.getMetrics(displaysMetrics)
60 | return displaysMetrics
61 | }
62 |
63 | /** dp转px */
64 | @JvmStatic
65 | fun dp2px(context: Context, dpVal: Float): Int {
66 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.resources.displayMetrics).toInt()
67 | }
68 |
69 | /**
70 | * 判断手机是否含有虚拟按键 99%
71 | */
72 | @JvmStatic
73 | fun hasVirtualNavigationBar(context: Context): Boolean {
74 | var hasSoftwareKeys = true
75 |
76 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
77 | val d = (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay
78 |
79 | val realDisplayMetrics = DisplayMetrics()
80 | d.getRealMetrics(realDisplayMetrics)
81 |
82 | val realHeight = realDisplayMetrics.heightPixels
83 | val realWidth = realDisplayMetrics.widthPixels
84 |
85 | val displayMetrics = DisplayMetrics()
86 | d.getMetrics(displayMetrics)
87 |
88 | val displayHeight = displayMetrics.heightPixels
89 | val displayWidth = displayMetrics.widthPixels
90 |
91 | hasSoftwareKeys = realWidth - displayWidth > 0 || realHeight - displayHeight > 0
92 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
93 | val hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey()
94 | val hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK)
95 | hasSoftwareKeys = !hasMenuKey && !hasBackKey
96 | }
97 |
98 | return hasSoftwareKeys
99 | }
100 |
101 | /**
102 | * 获取导航栏高度,有些没有虚拟导航栏的手机也能获取到,建议先判断是否有虚拟按键
103 | */
104 | @JvmStatic
105 | fun getNavigationBarHeight(context: Context): Int {
106 | val resourceId = context.resources.getIdentifier("navigation_bar_height", "dimen", "android")
107 | return if (resourceId > 0) context.resources.getDimensionPixelSize(resourceId) else 0
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/view/CropImageView.java:
--------------------------------------------------------------------------------
1 | package com.huburt.library.view;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapShader;
7 | import android.graphics.Canvas;
8 | import android.graphics.Matrix;
9 | import android.graphics.Paint;
10 | import android.graphics.Path;
11 | import android.graphics.PointF;
12 | import android.graphics.RectF;
13 | import android.graphics.Region;
14 | import android.graphics.Shader;
15 | import android.graphics.drawable.BitmapDrawable;
16 | import android.graphics.drawable.Drawable;
17 | import android.net.Uri;
18 | import android.os.Handler;
19 | import android.os.Looper;
20 | import android.os.Message;
21 | import android.support.v4.view.ViewCompat;
22 | import android.support.v7.widget.AppCompatImageView;
23 | import android.util.AttributeSet;
24 | import android.util.TypedValue;
25 | import android.view.MotionEvent;
26 |
27 |
28 | import com.huburt.library.R;
29 |
30 | import java.io.File;
31 | import java.io.IOException;
32 | import java.io.OutputStream;
33 | import java.text.SimpleDateFormat;
34 | import java.util.Date;
35 | import java.util.Locale;
36 |
37 | /**
38 | * ================================================
39 | * 作 者:廖子尧
40 | * 版 本:1.0
41 | * 创建日期:2016/1/7
42 | * 描 述:
43 | * Matrix 的9个值分别为 缩放 平移 倾斜
44 | * MSCALE_X MSKEW_X MTRANS_X
45 | * MSKEW_Y MSCALE_Y MTRANS_Y
46 | * MPERSP_0 MPERSP_1 MPERSP_2
47 | * 修订历史:
48 | * ================================================
49 | */
50 |
51 | public class CropImageView extends AppCompatImageView {
52 |
53 | /******************************** 中间的FocusView绘图相关的参数 *****************************/
54 | public enum Style {
55 | RECTANGLE, CIRCLE
56 | }
57 |
58 | private Style[] styles = {Style.RECTANGLE, Style.CIRCLE};
59 |
60 | private int mMaskColor = 0xAF000000; //暗色
61 | private int mBorderColor = 0xAA808080; //焦点框的边框颜色
62 | private int mBorderWidth = 1; //焦点边框的宽度(画笔宽度)
63 | private int mFocusWidth = 250; //焦点框的宽度
64 | private int mFocusHeight = 250; //焦点框的高度
65 | private int mDefaultStyleIndex = 0; //默认焦点框的形状
66 |
67 | private Style mStyle = styles[mDefaultStyleIndex];
68 | private Paint mBorderPaint = new Paint();
69 | private Path mFocusPath = new Path();
70 | private RectF mFocusRect = new RectF();
71 |
72 | /******************************** 图片缩放位移控制的参数 ************************************/
73 | private static final float MAX_SCALE = 4.0f; //最大缩放比,图片缩放后的大小与中间选中区域的比值
74 | private static final int NONE = 0; // 初始化
75 | private static final int DRAG = 1; // 拖拽
76 | private static final int ZOOM = 2; // 缩放
77 | private static final int ROTATE = 3; // 旋转
78 | private static final int ZOOM_OR_ROTATE = 4; // 缩放或旋转
79 |
80 | private static final int SAVE_SUCCESS = 1001; // 缩放或旋转
81 | private static final int SAVE_ERROR = 1002; // 缩放或旋转
82 |
83 | private int mImageWidth;
84 | private int mImageHeight;
85 | private int mRotatedImageWidth;
86 | private int mRotatedImageHeight;
87 | private Matrix matrix = new Matrix(); //图片变换的matrix
88 | private Matrix savedMatrix = new Matrix(); //开始变幻的时候,图片的matrix
89 | private PointF pA = new PointF(); //第一个手指按下点的坐标
90 | private PointF pB = new PointF(); //第二个手指按下点的坐标
91 | private PointF midPoint = new PointF(); //两个手指的中间点
92 | private PointF doubleClickPos = new PointF(); //双击图片的时候,双击点的坐标
93 | private PointF mFocusMidPoint = new PointF(); //中间View的中间点
94 | private int mode = NONE; //初始的模式
95 | private long doubleClickTime = 0; //第二次双击的时间
96 | private double rotation = 0; //手指旋转的角度,不是90的整数倍,可能为任意值,需要转换成level
97 | private float oldDist = 1; //双指第一次的距离
98 | private int sumRotateLevel = 0; //旋转的角度,90的整数倍
99 | private float mMaxScale = MAX_SCALE;//程序根据不同图片的大小,动态得到的最大缩放比
100 | private boolean isInited = false; //是否经过了 onSizeChanged 初始化
101 | private boolean mSaving = false; //是否正在保存
102 | private static Handler mHandler = new InnerHandler();
103 |
104 | public CropImageView(Context context) {
105 | this(context, null);
106 | }
107 |
108 | public CropImageView(Context context, AttributeSet attrs) {
109 | this(context, attrs, 0);
110 | }
111 |
112 | public CropImageView(Context context, AttributeSet attrs, int defStyle) {
113 | super(context, attrs, defStyle);
114 | mFocusWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mFocusWidth, getResources().getDisplayMetrics());
115 | mFocusHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mFocusHeight, getResources().getDisplayMetrics());
116 | mBorderWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mBorderWidth, getResources().getDisplayMetrics());
117 |
118 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CropImageView);
119 | mMaskColor = a.getColor(R.styleable.CropImageView_cropMaskColor, mMaskColor);
120 | mBorderColor = a.getColor(R.styleable.CropImageView_cropBorderColor, mBorderColor);
121 | mBorderWidth = a.getDimensionPixelSize(R.styleable.CropImageView_cropBorderWidth, mBorderWidth);
122 | mFocusWidth = a.getDimensionPixelSize(R.styleable.CropImageView_cropFocusWidth, mFocusWidth);
123 | mFocusHeight = a.getDimensionPixelSize(R.styleable.CropImageView_cropFocusHeight, mFocusHeight);
124 | mDefaultStyleIndex = a.getInteger(R.styleable.CropImageView_cropStyle, mDefaultStyleIndex);
125 | mStyle = styles[mDefaultStyleIndex];
126 | a.recycle();
127 |
128 | //只允许图片为当前的缩放模式
129 | setScaleType(ScaleType.MATRIX);
130 | }
131 |
132 | @Override
133 | public void setImageBitmap(Bitmap bm) {
134 | super.setImageBitmap(bm);
135 | initImage();
136 | }
137 |
138 | @Override
139 | public void setImageDrawable(Drawable drawable) {
140 | super.setImageDrawable(drawable);
141 | initImage();
142 | }
143 |
144 | @Override
145 | public void setImageResource(int resId) {
146 | super.setImageResource(resId);
147 | initImage();
148 | }
149 |
150 | @Override
151 | public void setImageURI(Uri uri) {
152 | super.setImageURI(uri);
153 | initImage();
154 | }
155 |
156 | @Override
157 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
158 | super.onSizeChanged(w, h, oldw, oldh);
159 | isInited = true;
160 | initImage();
161 | }
162 |
163 | /** 初始化图片和焦点框 */
164 | private void initImage() {
165 | Drawable d = getDrawable();
166 | if (!isInited || d == null) return;
167 |
168 | mode = NONE;
169 | matrix = getImageMatrix();
170 | mImageWidth = mRotatedImageWidth = d.getIntrinsicWidth();
171 | mImageHeight = mRotatedImageHeight = d.getIntrinsicHeight();
172 | //计算出焦点框的中点的坐标和上、下、左、右边的x或y的值
173 | int viewWidth = getWidth();
174 | int viewHeight = getHeight();
175 | float midPointX = viewWidth / 2;
176 | float midPointY = viewHeight / 2;
177 | mFocusMidPoint = new PointF(midPointX, midPointY);
178 |
179 | if (mStyle == Style.CIRCLE) {
180 | int focusSize = Math.min(mFocusWidth, mFocusHeight);
181 | mFocusWidth = focusSize;
182 | mFocusHeight = focusSize;
183 | }
184 | mFocusRect.left = mFocusMidPoint.x - mFocusWidth / 2;
185 | mFocusRect.right = mFocusMidPoint.x + mFocusWidth / 2;
186 | mFocusRect.top = mFocusMidPoint.y - mFocusHeight / 2;
187 | mFocusRect.bottom = mFocusMidPoint.y + mFocusHeight / 2;
188 |
189 | //适配焦点框的缩放比例(图片的最小边不小于焦点框的最小边)
190 | float fitFocusScale = getScale(mImageWidth, mImageHeight, mFocusWidth, mFocusHeight, true);
191 | mMaxScale = fitFocusScale * MAX_SCALE;
192 | //适配显示图片的ImageView的缩放比例(图片至少有一边是铺满屏幕的显示的情形)
193 | float fitViewScale = getScale(mImageWidth, mImageHeight, viewWidth, viewHeight, false);
194 | //确定最终的缩放比例,在适配焦点框的前提下适配显示图片的ImageView,
195 | //方案:首先满足适配焦点框,如果还能适配显示图片的ImageView,则适配它,即取缩放比例的最大值。
196 | //采取这种方案的原因:有可能图片很长或者很高,适配了ImageView的时候可能会宽/高已经小于焦点框的宽/高
197 | float scale = fitViewScale > fitFocusScale ? fitViewScale : fitFocusScale;
198 | //图像中点为中心进行缩放
199 | matrix.setScale(scale, scale, mImageWidth / 2, mImageHeight / 2);
200 | float[] mImageMatrixValues = new float[9];
201 | matrix.getValues(mImageMatrixValues); //获取缩放后的mImageMatrix的值
202 | float transX = mFocusMidPoint.x - (mImageMatrixValues[2] + mImageWidth * mImageMatrixValues[0] / 2); //X轴方向的位移
203 | float transY = mFocusMidPoint.y - (mImageMatrixValues[5] + mImageHeight * mImageMatrixValues[4] / 2); //Y轴方向的位移
204 | matrix.postTranslate(transX, transY);
205 | setImageMatrix(matrix);
206 | invalidate();
207 | }
208 |
209 | /** 计算边界缩放比例 isMinScale 是否最小比例,true 最小缩放比例, false 最大缩放比例 */
210 | private float getScale(int bitmapWidth, int bitmapHeight, int minWidth, int minHeight, boolean isMinScale) {
211 | float scale;
212 | float scaleX = (float) minWidth / bitmapWidth;
213 | float scaleY = (float) minHeight / bitmapHeight;
214 | if (isMinScale) {
215 | scale = scaleX > scaleY ? scaleX : scaleY;
216 | } else {
217 | scale = scaleX < scaleY ? scaleX : scaleY;
218 | }
219 | return scale;
220 | }
221 |
222 | /** 绘制焦点框 */
223 | @Override
224 | protected void onDraw(Canvas canvas) {
225 | super.onDraw(canvas);
226 | if (Style.RECTANGLE == mStyle) {
227 | mFocusPath.addRect(mFocusRect, Path.Direction.CCW);
228 | canvas.save();
229 | canvas.clipRect(0, 0, getWidth(), getHeight());
230 | canvas.clipPath(mFocusPath, Region.Op.DIFFERENCE);
231 | canvas.drawColor(mMaskColor);
232 | canvas.restore();
233 | } else if (Style.CIRCLE == mStyle) {
234 | float radius = Math.min((mFocusRect.right - mFocusRect.left) / 2, (mFocusRect.bottom - mFocusRect.top) / 2);
235 | mFocusPath.addCircle(mFocusMidPoint.x, mFocusMidPoint.y, radius, Path.Direction.CCW);
236 | canvas.save();
237 | canvas.clipRect(0, 0, getWidth(), getHeight());
238 | canvas.clipPath(mFocusPath, Region.Op.DIFFERENCE);
239 | canvas.drawColor(mMaskColor);
240 | canvas.restore();
241 | }
242 | mBorderPaint.setColor(mBorderColor);
243 | mBorderPaint.setStyle(Paint.Style.STROKE);
244 | mBorderPaint.setStrokeWidth(mBorderWidth);
245 | mBorderPaint.setAntiAlias(true);
246 | canvas.drawPath(mFocusPath, mBorderPaint);
247 | mFocusPath.reset();
248 | }
249 |
250 | @Override
251 | public boolean onTouchEvent(MotionEvent event) {
252 | if (mSaving || null == getDrawable()) {
253 | return super.onTouchEvent(event);
254 | }
255 | switch (event.getAction() & MotionEvent.ACTION_MASK) {
256 | case MotionEvent.ACTION_DOWN: //第一个点按下
257 | savedMatrix.set(matrix); //以后每次需要变换的时候,以现在的状态为基础进行变换
258 | pA.set(event.getX(), event.getY());
259 | pB.set(event.getX(), event.getY());
260 | mode = DRAG;
261 | break;
262 | case MotionEvent.ACTION_POINTER_DOWN: //第二个点按下
263 | if (event.getActionIndex() > 1) break;
264 | pA.set(event.getX(0), event.getY(0));
265 | pB.set(event.getX(1), event.getY(1));
266 | midPoint.set((pA.x + pB.x) / 2, (pA.y + pB.y) / 2);
267 | oldDist = spacing(pA, pB);
268 | savedMatrix.set(matrix); //以后每次需要变换的时候,以现在的状态为基础进行变换
269 | if (oldDist > 10f) mode = ZOOM_OR_ROTATE;//两点之间的距离大于10才有效
270 | break;
271 | case MotionEvent.ACTION_MOVE:
272 | if (mode == ZOOM_OR_ROTATE) {
273 | PointF pC = new PointF(event.getX(1) - event.getX(0) + pA.x, event.getY(1) - event.getY(0) + pA.y);
274 | double a = spacing(pB.x, pB.y, pC.x, pC.y);
275 | double b = spacing(pA.x, pA.y, pC.x, pC.y);
276 | double c = spacing(pA.x, pA.y, pB.x, pB.y);
277 | if (a >= 10) {
278 | double cosB = (a * a + c * c - b * b) / (2 * a * c);
279 | double angleB = Math.acos(cosB);
280 | double PID4 = Math.PI / 4;
281 | //旋转时,默认角度在 45 - 135 度之间
282 | if (angleB > PID4 && angleB < 3 * PID4) mode = ROTATE;
283 | else mode = ZOOM;
284 | }
285 | }
286 | if (mode == DRAG) {
287 | matrix.set(savedMatrix);
288 | matrix.postTranslate(event.getX() - pA.x, event.getY() - pA.y);
289 | fixTranslation();
290 | setImageMatrix(matrix);
291 | } else if (mode == ZOOM) {
292 | float newDist = spacing(event.getX(0), event.getY(0), event.getX(1), event.getY(1));
293 | if (newDist > 10f) {
294 | matrix.set(savedMatrix);
295 | // 这里之所以用 maxPostScale 矫正一下,主要是防止缩放到最大时,继续缩放图片会产生位移
296 | float tScale = Math.min(newDist / oldDist, maxPostScale());
297 | if (tScale != 0) {
298 | matrix.postScale(tScale, tScale, midPoint.x, midPoint.y);
299 | fixScale();
300 | fixTranslation();
301 | setImageMatrix(matrix);
302 | }
303 | }
304 | } else if (mode == ROTATE) {
305 | PointF pC = new PointF(event.getX(1) - event.getX(0) + pA.x, event.getY(1) - event.getY(0) + pA.y);
306 | double a = spacing(pB.x, pB.y, pC.x, pC.y);
307 | double b = spacing(pA.x, pA.y, pC.x, pC.y);
308 | double c = spacing(pA.x, pA.y, pB.x, pB.y);
309 | if (b > 10) {
310 | double cosA = (b * b + c * c - a * a) / (2 * b * c);
311 | double angleA = Math.acos(cosA);
312 | double ta = pB.y - pA.y;
313 | double tb = pA.x - pB.x;
314 | double tc = pB.x * pA.y - pA.x * pB.y;
315 | double td = ta * pC.x + tb * pC.y + tc;
316 | if (td > 0) {
317 | angleA = 2 * Math.PI - angleA;
318 | }
319 | rotation = angleA;
320 | matrix.set(savedMatrix);
321 | matrix.postRotate((float) (rotation * 180 / Math.PI), midPoint.x, midPoint.y);
322 | setImageMatrix(matrix);
323 | }
324 | }
325 | break;
326 | case MotionEvent.ACTION_UP:
327 | case MotionEvent.ACTION_POINTER_UP:
328 | if (mode == DRAG) {
329 | if (spacing(pA, pB) < 50) {
330 | long now = System.currentTimeMillis();
331 | if (now - doubleClickTime < 500 && spacing(pA, doubleClickPos) < 50) {
332 | doubleClick(pA.x, pA.y);
333 | now = 0;
334 | }
335 | doubleClickPos.set(pA);
336 | doubleClickTime = now;
337 | }
338 | } else if (mode == ROTATE) {
339 | int rotateLevel = (int) Math.floor((rotation + Math.PI / 4) / (Math.PI / 2));
340 | if (rotateLevel == 4) rotateLevel = 0;
341 | matrix.set(savedMatrix);
342 | matrix.postRotate(90 * rotateLevel, midPoint.x, midPoint.y);
343 | if (rotateLevel == 1 || rotateLevel == 3) {
344 | int tmp = mRotatedImageWidth;
345 | mRotatedImageWidth = mRotatedImageHeight;
346 | mRotatedImageHeight = tmp;
347 | }
348 | fixScale();
349 | fixTranslation();
350 | setImageMatrix(matrix);
351 | sumRotateLevel += rotateLevel;
352 | }
353 | mode = NONE;
354 | break;
355 | }
356 | //解决部分机型无法拖动的问题
357 | ViewCompat.postInvalidateOnAnimation(this);
358 | return true;
359 | }
360 |
361 | /** 修正图片的缩放比 */
362 | private void fixScale() {
363 | float imageMatrixValues[] = new float[9];
364 | matrix.getValues(imageMatrixValues);
365 | float currentScale = Math.abs(imageMatrixValues[0]) + Math.abs(imageMatrixValues[1]);
366 | float minScale = getScale(mRotatedImageWidth, mRotatedImageHeight, mFocusWidth, mFocusHeight, true);
367 | mMaxScale = minScale * MAX_SCALE;
368 |
369 | //保证图片最小是占满中间的焦点空间
370 | if (currentScale < minScale) {
371 | float scale = minScale / currentScale;
372 | matrix.postScale(scale, scale);
373 | } else if (currentScale > mMaxScale) {
374 | float scale = mMaxScale / currentScale;
375 | matrix.postScale(scale, scale);
376 | }
377 | }
378 |
379 | /** 修正图片的位移 */
380 | private void fixTranslation() {
381 | RectF imageRect = new RectF(0, 0, mImageWidth, mImageHeight);
382 | matrix.mapRect(imageRect); //获取当前图片(缩放以后的)相对于当前控件的位置区域,超过控件的上边缘或左边缘为负
383 | float deltaX = 0, deltaY = 0;
384 | if (imageRect.left > mFocusRect.left) {
385 | deltaX = -imageRect.left + mFocusRect.left;
386 | } else if (imageRect.right < mFocusRect.right) {
387 | deltaX = -imageRect.right + mFocusRect.right;
388 | }
389 | if (imageRect.top > mFocusRect.top) {
390 | deltaY = -imageRect.top + mFocusRect.top;
391 | } else if (imageRect.bottom < mFocusRect.bottom) {
392 | deltaY = -imageRect.bottom + mFocusRect.bottom;
393 | }
394 | matrix.postTranslate(deltaX, deltaY);
395 | }
396 |
397 | /** 获取当前图片允许的最大缩放比 */
398 | private float maxPostScale() {
399 | float imageMatrixValues[] = new float[9];
400 | matrix.getValues(imageMatrixValues);
401 | float curScale = Math.abs(imageMatrixValues[0]) + Math.abs(imageMatrixValues[1]);
402 | return mMaxScale / curScale;
403 | }
404 |
405 | /** 计算两点之间的距离 */
406 | private float spacing(float x1, float y1, float x2, float y2) {
407 | float x = x1 - x2;
408 | float y = y1 - y2;
409 | return (float) Math.sqrt(x * x + y * y);
410 | }
411 |
412 | /** 计算两点之间的距离 */
413 | private float spacing(PointF pA, PointF pB) {
414 | return spacing(pA.x, pA.y, pB.x, pB.y);
415 | }
416 |
417 | /** 双击触发的方法 */
418 | private void doubleClick(float x, float y) {
419 | float p[] = new float[9];
420 | matrix.getValues(p);
421 | float curScale = Math.abs(p[0]) + Math.abs(p[1]);
422 | float minScale = getScale(mRotatedImageWidth, mRotatedImageHeight, mFocusWidth, mFocusHeight, true);
423 | if (curScale < mMaxScale) {
424 | //每次双击的时候,缩放加 minScale
425 | float toScale = Math.min(curScale + minScale, mMaxScale) / curScale;
426 | matrix.postScale(toScale, toScale, x, y);
427 | } else {
428 | float toScale = minScale / curScale;
429 | matrix.postScale(toScale, toScale, x, y);
430 | fixTranslation();
431 | }
432 | setImageMatrix(matrix);
433 | }
434 |
435 | /**
436 | * @param expectWidth 期望的宽度
437 | * @param exceptHeight 期望的高度
438 | * @param isSaveRectangle 是否按矩形区域保存图片
439 | * @return 裁剪后的Bitmap
440 | */
441 | public Bitmap getCropBitmap(int expectWidth, int exceptHeight, boolean isSaveRectangle) {
442 | if (expectWidth <= 0 || exceptHeight < 0) return null;
443 | Bitmap srcBitmap = ((BitmapDrawable) getDrawable()).getBitmap();
444 | srcBitmap = rotate(srcBitmap, sumRotateLevel * 90); //最好用level,因为角度可能不是90的整数
445 | return makeCropBitmap(srcBitmap, mFocusRect, getImageMatrixRect(), expectWidth, exceptHeight, isSaveRectangle);
446 | }
447 |
448 | /**
449 | * @param bitmap 要旋转的图片
450 | * @param degrees 选择的角度(单位 度)
451 | * @return 旋转后的Bitmap
452 | */
453 | public Bitmap rotate(Bitmap bitmap, int degrees) {
454 | if (degrees != 0 && bitmap != null) {
455 | Matrix matrix = new Matrix();
456 | matrix.setRotate(degrees, (float) bitmap.getWidth() / 2, (float) bitmap.getHeight() / 2);
457 | try {
458 | Bitmap rotateBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
459 | if (bitmap != rotateBitmap) {
460 | // bitmap.recycle();
461 | return rotateBitmap;
462 | }
463 | } catch (OutOfMemoryError ex) {
464 | ex.printStackTrace();
465 | }
466 | }
467 | return bitmap;
468 | }
469 |
470 | /**
471 | * @return 获取当前图片显示的矩形区域
472 | */
473 | private RectF getImageMatrixRect() {
474 | RectF rectF = new RectF();
475 | rectF.set(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight());
476 | matrix.mapRect(rectF);
477 | return rectF;
478 | }
479 |
480 | /**
481 | * @param bitmap 需要裁剪的图片
482 | * @param focusRect 中间需要裁剪的矩形区域
483 | * @param imageMatrixRect 当前图片在屏幕上的显示矩形区域
484 | * @param expectWidth 希望获得的图片宽度,如果图片宽度不足时,拉伸图片
485 | * @param exceptHeight 希望获得的图片高度,如果图片高度不足时,拉伸图片
486 | * @param isSaveRectangle 是否希望按矩形区域保存图片
487 | * @return 裁剪后的图片的Bitmap
488 | */
489 | private Bitmap makeCropBitmap(Bitmap bitmap, RectF focusRect, RectF imageMatrixRect, int expectWidth, int exceptHeight, boolean isSaveRectangle) {
490 | if (imageMatrixRect == null || bitmap == null){
491 | return null;
492 | }
493 | float scale = imageMatrixRect.width() / bitmap.getWidth();
494 | int left = (int) ((focusRect.left - imageMatrixRect.left) / scale);
495 | int top = (int) ((focusRect.top - imageMatrixRect.top) / scale);
496 | int width = (int) (focusRect.width() / scale);
497 | int height = (int) (focusRect.height() / scale);
498 |
499 | if (left < 0) left = 0;
500 | if (top < 0) top = 0;
501 | if (left + width > bitmap.getWidth()) width = bitmap.getWidth() - left;
502 | if (top + height > bitmap.getHeight()) height = bitmap.getHeight() - top;
503 |
504 | try {
505 | bitmap = Bitmap.createBitmap(bitmap, left, top, width, height);
506 | if (expectWidth != width || exceptHeight != height) {
507 | bitmap = Bitmap.createScaledBitmap(bitmap, expectWidth, exceptHeight, true);
508 | if (mStyle == CropImageView.Style.CIRCLE && !isSaveRectangle) {
509 | //如果是圆形,就将图片裁剪成圆的
510 | int length = Math.min(expectWidth, exceptHeight);
511 | int radius = length / 2;
512 | Bitmap circleBitmap = Bitmap.createBitmap(length, length, Bitmap.Config.ARGB_8888);
513 | Canvas canvas = new Canvas(circleBitmap);
514 | BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
515 | Paint paint = new Paint();
516 | paint.setShader(bitmapShader);
517 | canvas.drawCircle(expectWidth / 2f, exceptHeight / 2f, radius, paint);
518 | bitmap = circleBitmap;
519 | }
520 | }
521 | } catch (OutOfMemoryError e) {
522 | e.printStackTrace();
523 | }
524 | return bitmap;
525 | }
526 |
527 | /**
528 | * @param folder 希望保存的文件夹
529 | * @param expectWidth 希望保存的图片宽度
530 | * @param exceptHeight 希望保存的图片高度
531 | * @param isSaveRectangle 是否希望按矩形区域保存图片
532 | */
533 | public void saveBitmapToFile(File folder, int expectWidth, int exceptHeight, boolean isSaveRectangle) {
534 | if (mSaving) return;
535 | mSaving = true;
536 | final Bitmap croppedImage = getCropBitmap(expectWidth, exceptHeight, isSaveRectangle);
537 | Bitmap.CompressFormat outputFormat = Bitmap.CompressFormat.JPEG;
538 | File saveFile = createFile(folder, "IMG_", ".jpg");
539 | if (mStyle == CropImageView.Style.CIRCLE && !isSaveRectangle) {
540 | outputFormat = Bitmap.CompressFormat.PNG;
541 | saveFile = createFile(folder, "IMG_", ".png");
542 | }
543 | final Bitmap.CompressFormat finalOutputFormat = outputFormat;
544 | final File finalSaveFile = saveFile;
545 | new Thread() {
546 | @Override
547 | public void run() {
548 | saveOutput(croppedImage, finalOutputFormat, finalSaveFile);
549 | }
550 | }.start();
551 | }
552 |
553 | /** 根据系统时间、前缀、后缀产生一个文件 */
554 | private File createFile(File folder, String prefix, String suffix) {
555 | if (!folder.exists() || !folder.isDirectory()) folder.mkdirs();
556 | try {
557 | File nomedia = new File(folder, ".nomedia"); //在当前文件夹底下创建一个 .nomedia 文件
558 | if (!nomedia.exists()) nomedia.createNewFile();
559 | } catch (IOException e) {
560 | e.printStackTrace();
561 | }
562 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.CHINA);
563 | String filename = prefix + dateFormat.format(new Date(System.currentTimeMillis())) + suffix;
564 | return new File(folder, filename);
565 | }
566 |
567 | /** 将图片保存在本地 */
568 | private void saveOutput(Bitmap croppedImage, Bitmap.CompressFormat outputFormat, File saveFile) {
569 | OutputStream outputStream = null;
570 | try {
571 | outputStream = getContext().getContentResolver().openOutputStream(Uri.fromFile(saveFile));
572 | if (outputStream != null) croppedImage.compress(outputFormat, 90, outputStream);
573 | Message.obtain(mHandler, SAVE_SUCCESS, saveFile).sendToTarget();
574 | } catch (IOException ex) {
575 | ex.printStackTrace();
576 | Message.obtain(mHandler, SAVE_ERROR, saveFile).sendToTarget();
577 | } finally {
578 | if (outputStream != null) {
579 | try {
580 | outputStream.close();
581 | } catch (IOException e) {
582 | e.printStackTrace();
583 | }
584 | }
585 | }
586 | mSaving = false;
587 | croppedImage.recycle();
588 | }
589 |
590 | private static class InnerHandler extends Handler {
591 | public InnerHandler() {
592 | super(Looper.getMainLooper());
593 | }
594 |
595 | @Override
596 | public void handleMessage(Message msg) {
597 | File saveFile = (File) msg.obj;
598 | switch (msg.what) {
599 | case SAVE_SUCCESS:
600 | if (mListener != null) mListener.onBitmapSaveSuccess(saveFile);
601 | break;
602 | case SAVE_ERROR:
603 | if (mListener != null) mListener.onBitmapSaveError(saveFile);
604 | break;
605 | }
606 | }
607 | }
608 |
609 | /** 图片保存完成的监听 */
610 | private static OnBitmapSaveCompleteListener mListener;
611 |
612 | public interface OnBitmapSaveCompleteListener {
613 | void onBitmapSaveSuccess(File file);
614 |
615 | void onBitmapSaveError(File file);
616 | }
617 |
618 | public void setOnBitmapSaveCompleteListener(OnBitmapSaveCompleteListener listener) {
619 | mListener = listener;
620 | }
621 |
622 | /** 返回焦点框宽度 */
623 | public int getFocusWidth() {
624 | return mFocusWidth;
625 | }
626 |
627 | /** 设置焦点框的宽度 */
628 | public void setFocusWidth(int width) {
629 | mFocusWidth = width;
630 | initImage();
631 | }
632 |
633 | /** 获取焦点框的高度 */
634 | public int getFocusHeight() {
635 | return mFocusHeight;
636 | }
637 |
638 | /** 设置焦点框的高度 */
639 | public void setFocusHeight(int height) {
640 | mFocusHeight = height;
641 | initImage();
642 | }
643 |
644 | /** 返回阴影颜色 */
645 | public int getMaskColor() {
646 | return mMaskColor;
647 | }
648 |
649 | /** 设置阴影颜色 */
650 | public void setMaskColor(int color) {
651 | mMaskColor = color;
652 | invalidate();
653 | }
654 |
655 | /** 返回焦点框边框颜色 */
656 | public int getFocusColor() {
657 | return mBorderColor;
658 | }
659 |
660 | /** 设置焦点框边框颜色 */
661 | public void setBorderColor(int color) {
662 | mBorderColor = color;
663 | invalidate();
664 | }
665 |
666 | /** 返回焦点框边框绘制宽度 */
667 | public float getBorderWidth() {
668 | return mBorderWidth;
669 | }
670 |
671 | /** 设置焦点边框宽度 */
672 | public void setBorderWidth(int width) {
673 | mBorderWidth = width;
674 | invalidate();
675 | }
676 |
677 | /** 设置焦点框的形状 */
678 | public void setFocusStyle(Style style) {
679 | this.mStyle = style;
680 | invalidate();
681 | }
682 |
683 | /** 获取焦点框的形状 */
684 | public Style getFocusStyle() {
685 | return mStyle;
686 | }
687 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/view/FolderPopUpWindow.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.view
2 |
3 | import android.animation.Animator
4 | import android.animation.AnimatorListenerAdapter
5 | import android.animation.AnimatorSet
6 | import android.animation.ObjectAnimator
7 | import android.content.Context
8 | import android.graphics.drawable.ColorDrawable
9 | import android.util.Log
10 | import android.view.View
11 | import android.view.ViewGroup
12 | import android.view.ViewTreeObserver
13 | import android.view.animation.AccelerateDecelerateInterpolator
14 | import android.widget.AdapterView
15 | import android.widget.BaseAdapter
16 | import android.widget.LinearLayout
17 | import android.widget.ListView
18 | import android.widget.PopupWindow
19 |
20 | import com.huburt.library.R
21 |
22 | class FolderPopUpWindow(context: Context, adapter: BaseAdapter
23 | ) : PopupWindow(context), View.OnClickListener {
24 |
25 | private val listView: ListView
26 | private var onItemClickListener: OnItemClickListener? = null
27 | private val masker: View
28 | private val marginView: View
29 | private var marginPx: Int = 0
30 | private var outSet: AnimatorSet? = null
31 | private var enterSet: AnimatorSet? = null
32 |
33 | init {
34 |
35 | val view = View.inflate(context, R.layout.pop_folder, null)
36 | masker = view.findViewById(R.id.masker)
37 | masker.setOnClickListener(this)
38 | marginView = view.findViewById(R.id.margin)
39 | marginView.setOnClickListener(this)
40 | listView = view.findViewById(R.id.listView) as ListView
41 | listView.adapter = adapter
42 |
43 | contentView = view
44 | width = ViewGroup.LayoutParams.MATCH_PARENT //如果不设置,就是 AnchorView 的宽度
45 | height = ViewGroup.LayoutParams.MATCH_PARENT
46 | isFocusable = true
47 | isOutsideTouchable = true
48 | setBackgroundDrawable(ColorDrawable(0))
49 | animationStyle = 0
50 | view.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
51 | override fun onGlobalLayout() {
52 | view.viewTreeObserver.removeOnGlobalLayoutListener(this)
53 | Log.e("hubert", "view created")
54 | val maxHeight = view.height * 5 / 8
55 | val realHeight = listView.height
56 | val listParams = listView.layoutParams
57 | listParams.height = if (realHeight > maxHeight) maxHeight else realHeight
58 | listView.layoutParams = listParams
59 | val marginParams = marginView.layoutParams as LinearLayout.LayoutParams
60 | marginParams.height = marginPx
61 | marginView.layoutParams = marginParams
62 |
63 | initEnterSet()
64 | enterSet?.start()
65 | }
66 | })
67 | listView.onItemClickListener = AdapterView.OnItemClickListener { adapterView, v, position, l -> onItemClickListener?.onItemClick(adapterView, v, position, l) }
68 | }
69 |
70 | private fun initEnterSet() {
71 | val alpha = ObjectAnimator.ofFloat(masker, "alpha", 0f, 1f)
72 | val translationY = ObjectAnimator.ofFloat(listView, "translationY", listView.height.toFloat(), 0f)
73 | enterSet = AnimatorSet()
74 | enterSet!!.duration = 400
75 | enterSet!!.playTogether(alpha, translationY)
76 | enterSet!!.interpolator = AccelerateDecelerateInterpolator()
77 | }
78 |
79 | override fun showAtLocation(parent: View, gravity: Int, x: Int, y: Int) {
80 | super.showAtLocation(parent, gravity, x, y)
81 | enterSet?.start()
82 | }
83 |
84 | override fun dismiss() {
85 | exitAnimator()
86 | }
87 |
88 | private fun exitAnimator() {
89 | if (outSet == null) {
90 | val alpha = ObjectAnimator.ofFloat(masker, "alpha", 1f, 0f)
91 | val translationY = ObjectAnimator.ofFloat(listView, "translationY", 0f, listView.height.toFloat())
92 | outSet = AnimatorSet()
93 | outSet!!.duration = 300
94 | outSet!!.playTogether(alpha, translationY)
95 | outSet!!.interpolator = AccelerateDecelerateInterpolator()
96 | outSet!!.addListener(object : AnimatorListenerAdapter() {
97 | override fun onAnimationStart(animation: Animator) {
98 | listView.visibility = View.VISIBLE
99 | }
100 |
101 | override fun onAnimationEnd(animation: Animator) {
102 | super@FolderPopUpWindow.dismiss()
103 | }
104 | })
105 | }
106 | //防止重复点击
107 | if (outSet!!.isRunning) {
108 | return
109 | }
110 | outSet!!.start()
111 | }
112 |
113 | fun setOnItemClickListener(listener: OnItemClickListener) {
114 | this.onItemClickListener = listener
115 | }
116 |
117 | fun setSelection(selection: Int) {
118 | listView.setSelection(selection)
119 | }
120 |
121 | fun setMargin(marginPx: Int) {
122 | this.marginPx = marginPx
123 | }
124 |
125 | override fun onClick(v: View) {
126 | dismiss()
127 | }
128 |
129 | interface OnItemClickListener {
130 | fun onItemClick(adapterView: AdapterView<*>, view: View, position: Int, l: Long)
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/library/src/main/java/com/huburt/library/view/GridSpacingItemDecoration.kt:
--------------------------------------------------------------------------------
1 | package com.huburt.library.view
2 |
3 | import android.graphics.Rect
4 | import android.support.v7.widget.RecyclerView
5 | import android.view.View
6 |
7 | class GridSpacingItemDecoration(
8 | private val spanCount: Int,
9 | private val spacing: Int,
10 | private val includeEdge: Boolean
11 | ) : RecyclerView.ItemDecoration() {
12 |
13 | override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State?) {
14 | val position = parent.getChildAdapterPosition(view) // item position
15 | val column = position % spanCount // item column
16 |
17 | if (includeEdge) {
18 | outRect.left = spacing - column * spacing / spanCount // spacing - column * ((1f / spanCount) * spacing)
19 | outRect.right = (column + 1) * spacing / spanCount // (column + 1) * ((1f / spanCount) * spacing)
20 |
21 | if (position < spanCount) { // top edge
22 | outRect.top = spacing
23 | }
24 | outRect.bottom = spacing // item bottom
25 | } else {
26 | outRect.left = column * spacing / spanCount // column * ((1f / spanCount) * spacing)
27 | outRect.right = spacing - (column + 1) * spacing / spanCount // spacing - (column + 1) * ((1f / spanCount) * spacing)
28 | if (position >= spanCount) {
29 | outRect.top = spacing // item top
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/library/src/main/res/anim/fade_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
--------------------------------------------------------------------------------
/library/src/main/res/anim/fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
--------------------------------------------------------------------------------
/library/src/main/res/anim/hide_to_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/library/src/main/res/anim/show_from_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/library/src/main/res/anim/top_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/library/src/main/res/anim/top_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable-v21/bg_folder_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | -
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/bg_btn_dis.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/bg_btn_nor.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/bg_btn_pre.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/bg_folder_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/bg_image_folder.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 | -
10 |
11 |
12 |
13 |
14 | -
15 |
16 |
17 |
18 |
19 | -
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/ic_arrow_back.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/ic_cover_shade.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/ic_default_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/ic_vector_check.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/ic_vector_delete.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/selector_back_press.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/selector_grid_camera_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/selector_item_checked.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/selector_top_ok.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/shape_frame.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/activity_image_crop.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/activity_image_grid.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
12 |
13 |
17 |
18 |
27 |
28 |
34 |
35 |
42 |
43 |
54 |
55 |
63 |
64 |
65 |
66 |
79 |
80 |
81 |
82 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/activity_image_preview.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
13 |
14 |
17 |
18 |
27 |
28 |
35 |
36 |
37 |
38 |
42 |
43 |
46 |
47 |
57 |
58 |
71 |
72 |
73 |
78 |
79 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/adapter_camera_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/adapter_folder_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
24 |
25 |
26 |
27 |
33 |
34 |
41 |
42 |
50 |
51 |
52 |
59 |
60 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/adapter_image_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
18 |
19 |
25 |
26 |
37 |
38 |
47 |
48 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/include_top_bar.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
25 |
26 |
37 |
38 |
54 |
55 |
65 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/item_small_preview.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
16 |
17 |
22 |
23 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/pop_folder.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
22 |
30 |
31 |
32 |
37 |
--------------------------------------------------------------------------------
/library/src/main/res/mipmap-xxhdpi/checkbox_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/library/src/main/res/mipmap-xxhdpi/checkbox_checked.png
--------------------------------------------------------------------------------
/library/src/main/res/mipmap-xxhdpi/checkbox_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/library/src/main/res/mipmap-xxhdpi/checkbox_normal.png
--------------------------------------------------------------------------------
/library/src/main/res/mipmap-xxhdpi/grid_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/library/src/main/res/mipmap-xxhdpi/grid_camera.png
--------------------------------------------------------------------------------
/library/src/main/res/mipmap-xxhdpi/text_indicator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/library/src/main/res/mipmap-xxhdpi/text_indicator.png
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/library/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #323336
4 | #393A3F
5 | #303135
6 | #ec2f3335
7 | #ffe0e0e0
8 | #353535
9 | #1AAD19
10 | #66ffffff
11 | #ffffff
12 |
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 所有图片
3 | 共%1$d张
4 | %1$d/%2$d
5 | 最多选择%1$d张图片
6 | 完成
7 | 完成(%1$d/%2$d)
8 | 预览
9 | 预览(%1$d)
10 | 图片裁剪
11 | 原图
12 | 原图(%1$s)
13 |
14 |
15 |
--------------------------------------------------------------------------------
/library/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
11 |
12 |
13 |
16 |
17 |
23 |
--------------------------------------------------------------------------------
/library/src/main/res/xml/provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/screen/device-2017-10-16-095841.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/screen/device-2017-10-16-095841.png
--------------------------------------------------------------------------------
/screen/device-2017-11-01-110941.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/screen/device-2017-11-01-110941.png
--------------------------------------------------------------------------------
/screen/device-2017-11-01-111126.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/screen/device-2017-11-01-111126.png
--------------------------------------------------------------------------------
/screen/device-2017-11-01-111209.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/screen/device-2017-11-01-111209.png
--------------------------------------------------------------------------------
/screen/show_time.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/screen/show_time.png
--------------------------------------------------------------------------------
/screen/small_preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huburt-Hu/ImagePicker/e0dc061f7311d4b0bfd18bc587d61350e6f1c401/screen/small_preview.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':library'
2 |
--------------------------------------------------------------------------------