list) {
95 | zfileQwBar.setVisibility(View.GONE);
96 | if (list == null || list.isEmpty()) {
97 | qwAdapter.clear();
98 | zfileQwEmptyLayout.setVisibility(View.VISIBLE);
99 | } else {
100 | qwAdapter.setDatas(list);
101 | zfileQwEmptyLayout.setVisibility(View.GONE);
102 | }
103 | }
104 | });
105 | zFileQWAsync.start(filterArray);
106 | }
107 |
108 | private void initAdapter() {
109 | if (qwAdapter == null) {
110 | qwAdapter = new ZFileListAdapter(getContext(), true);
111 | qwAdapter.setItemClickByAnim(new ZFileListAdapter.ItemClickByAnim() {
112 | @Override
113 | public void onClick(View view, int position, ZFileBean bean) {
114 | ZFileUtil.openFile(bean.getFilePath(), view);
115 | }
116 | });
117 | qwAdapter.setQwListener(new ZFileListAdapter.QWListener() {
118 | @Override
119 | public void invoke(boolean isManage, ZFileBean bean, boolean isSelect) {
120 | if (isManage) {
121 | ((ZFileQWActivity) Objects.requireNonNull(getActivity())).observer(ZFileContent.toQWBean(bean, isSelect));
122 | }
123 | }
124 | });
125 | qwAdapter.setManage(qwManage);
126 | }
127 | }
128 |
129 | public void setManager(boolean isManage) {
130 | if (this.qwManage != isManage) {
131 | this.qwManage = isManage;
132 | if (qwAdapter != null) {
133 | qwAdapter.setManage(isManage);
134 | }
135 | }
136 | }
137 |
138 | public void removeLastSelectData(ZFileBean bean) {
139 | if (qwAdapter != null) {
140 | qwAdapter.setQWLastState(bean);
141 | }
142 | }
143 |
144 | public void resetAll() {
145 | qwManage = false;
146 | if (qwAdapter != null) {
147 | qwAdapter.setManage(false);
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/mavencentral.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven-publish'
2 | apply plugin: 'signing'
3 |
4 | //解决 gradlew 构建错误: 编码 GBK的不可映射字符
5 | tasks.withType(Javadoc) {
6 | // options.encoding = "UTF-8"
7 | options.addStringOption('Xdoclint:none', '-quiet')
8 | options.addStringOption('encoding', 'UTF-8')
9 | options.addStringOption('charSet', 'UTF-8')
10 | }
11 |
12 | task androidJavadocs(type: Javadoc) {
13 | source = android.sourceSets.main.java.source
14 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
15 | exclude "**/R.class"
16 | exclude "**/BuildConfig.class"
17 | }
18 |
19 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
20 | classifier = 'javadoc'
21 | from androidJavadocs.destinationDir
22 | }
23 |
24 | task androidSourcesJar(type: Jar) {
25 | classifier = 'sources'
26 | from android.sourceSets.main.java.source
27 | }
28 |
29 | ext["signing.keyId"] = ''
30 | ext["signing.password"] = ''
31 | ext["signing.secretKeyRingFile"] = ''
32 | ext["ossrhUsername"] = ''
33 | ext["ossrhPassword"] = ''
34 |
35 | File secretPropsFile = project.rootProject.file('local.properties')
36 | if (secretPropsFile.exists()) {
37 | println "Found secret props file, loading props"
38 | Properties p = new Properties()
39 | p.load(new FileInputStream(secretPropsFile))
40 | p.each { name, value ->
41 | ext[name] = value
42 | }
43 | } else {
44 | println "No props file, loading env vars"
45 | }
46 | publishing {
47 | publications {
48 | release(MavenPublication) {
49 | // The coordinates of the library, being set from variables that
50 | // we'll set up in a moment
51 | groupId PUBLISH_GROUP_ID
52 | artifactId PUBLISH_ARTIFACT_ID
53 | version PUBLISH_VERSION
54 |
55 | // Two artifacts, the `aar` and the sources
56 | artifact("$buildDir/outputs/aar/${project.getName()}-release.aar")
57 | artifact androidSourcesJar
58 | artifact androidJavadocsJar
59 |
60 | // Self-explanatory metadata for the most part
61 | pom {
62 | // packaging 'aar'
63 | name = PUBLISH_ARTIFACT_ID
64 | description = '你的项目描述'
65 | // If your project has a dedicated site, use its URL here
66 | url = 'Github地址'
67 | licenses {
68 | license {
69 | //协议类型,一般默认Apache License2.0的话不用改:
70 | name = 'The Apache License, Version 2.0'
71 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
72 | }
73 | }
74 | developers {
75 | developer {
76 | // id = '用户ID'
77 | name = 'Kathline'
78 | email = '1350624667@qq.com'
79 | }
80 | }
81 | // Version control info, if you're using GitHub, follow the format as seen here
82 | scm {
83 | //修改成你的Git地址:
84 | connection = 'scm:git:github.com/xxx/xxxx.git'
85 | developerConnection = 'scm:git:ssh://github.com/xxx/xxxx.git'
86 | //分支地址:
87 | url = 'https://github.com/xxx/xxxx/tree/master'
88 | }
89 | // A slightly hacky fix so that your POM will include any transitive dependencies
90 | // that your library builds upon
91 | withXml {
92 | def dependenciesNode = asNode().appendNode('dependencies')
93 | project.configurations.all { configuration ->
94 | def name = configuration.name
95 | if (name != "implementation" && name != "compile" && name != "api") {
96 | return
97 | }
98 | println(configuration)
99 | configuration.dependencies.each {
100 | println(it)
101 | if (it.name == "unspecified") {
102 | // 忽略无法识别的
103 | return
104 | }
105 | def dependencyNode = dependenciesNode.appendNode('dependency')
106 | dependencyNode.appendNode('groupId', it.group)
107 | dependencyNode.appendNode('artifactId', it.name)
108 | dependencyNode.appendNode('version', it.version)
109 | if (name == "api" || name == "compile") {
110 | dependencyNode.appendNode("scope", "compile")
111 | } else { // implementation
112 | dependencyNode.appendNode("scope", "runtime")
113 | }
114 | }
115 | }
116 | }
117 | }
118 | }
119 | }
120 | repositories {
121 | // The repository to publish to, Sonatype/MavenCentral
122 | maven {
123 | // This is an arbitrary name, you may also use "mavencentral" or
124 | // any other name that's descriptive for you
125 | name = PUBLISH_ARTIFACT_ID
126 |
127 | def releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
128 | def snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
129 | // You only need this if you want to publish snapshots, otherwise just set the URL
130 | // to the release repo directly
131 | url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
132 |
133 | // The username and password we've fetched earlier
134 | credentials {
135 | username ossrhUsername
136 | password ossrhPassword
137 | }
138 | }
139 | }
140 | }
141 | signing {
142 | sign publishing.publications
143 | }
--------------------------------------------------------------------------------
/filePicker/src/main/java/com/kathline/library/ui/dialog/ZFileSortDialog.java:
--------------------------------------------------------------------------------
1 | package com.kathline.library.ui.dialog;
2 |
3 | import android.app.Dialog;
4 | import android.os.Bundle;
5 | import android.view.Gravity;
6 | import android.view.View;
7 | import android.view.Window;
8 | import android.widget.Button;
9 | import android.widget.LinearLayout;
10 | import android.widget.RadioButton;
11 | import android.widget.RadioGroup;
12 | import android.widget.TextView;
13 |
14 | import androidx.annotation.NonNull;
15 | import androidx.annotation.Nullable;
16 |
17 | import com.kathline.library.R;
18 | import com.kathline.library.common.ZFileManageDialog;
19 | import com.kathline.library.content.ZFileContent;
20 |
21 | public class ZFileSortDialog extends ZFileManageDialog implements RadioGroup.OnCheckedChangeListener {
22 |
23 | private int sortSelectId = 0;
24 | private int sequenceSelectId = 0;
25 |
26 | private RadioGroup zfileSortGroup;
27 | private RadioButton zfileSortByDefault;
28 | private RadioButton zfileSortByName;
29 | private RadioButton zfileSortByDate;
30 | private RadioButton zfileSortBySize;
31 | private LinearLayout zfileSequenceLayout;
32 | private RadioGroup zfileSequenceGroup;
33 | private RadioButton zfileSequenceAsc;
34 | private RadioButton zfileSequenceDesc;
35 | private Button zfileDialogSortDown;
36 | private TextView zfileDialogSortCancel;
37 |
38 | private void initView() {
39 | zfileSortGroup = (RadioGroup) view.findViewById(R.id.zfile_sortGroup);
40 | zfileSortByDefault = (RadioButton) view.findViewById(R.id.zfile_sort_by_default);
41 | zfileSortByName = (RadioButton) view.findViewById(R.id.zfile_sort_by_name);
42 | zfileSortByDate = (RadioButton) view.findViewById(R.id.zfile_sort_by_date);
43 | zfileSortBySize = (RadioButton) view.findViewById(R.id.zfile_sort_by_size);
44 | zfileSequenceLayout = (LinearLayout) view.findViewById(R.id.zfile_sequenceLayout);
45 | zfileSequenceGroup = (RadioGroup) view.findViewById(R.id.zfile_sequenceGroup);
46 | zfileSequenceAsc = (RadioButton) view.findViewById(R.id.zfile_sequence_asc);
47 | zfileSequenceDesc = (RadioButton) view.findViewById(R.id.zfile_sequence_desc);
48 | zfileDialogSortDown = (Button) view.findViewById(R.id.zfile_dialog_sort_down);
49 | zfileDialogSortCancel = (TextView) view.findViewById(R.id.zfile_dialog_sort_cancel);
50 | }
51 |
52 | public interface CheckedChangedListener {
53 | void onCheckedChange(int sortSelectId, int sequenceSelectId);
54 | }
55 |
56 | private CheckedChangedListener checkedChangedListener;
57 |
58 | public void setCheckedChangedListener(CheckedChangedListener listener) {
59 | checkedChangedListener = listener;
60 | }
61 |
62 | public static ZFileSortDialog newInstance(int sortSelectId, int sequenceSelectId) {
63 | Bundle bundle = new Bundle();
64 | bundle.putInt("sortSelectId", sortSelectId);
65 | bundle.putInt("sequenceSelectId", sequenceSelectId);
66 | ZFileSortDialog fragment = new ZFileSortDialog();
67 | fragment.setArguments(bundle);
68 | return fragment;
69 | }
70 |
71 | @Override
72 | public void onCheckedChanged(RadioGroup group, int checkedId) {
73 | if (group.getId() == R.id.zfile_sortGroup) { // 方式
74 | sortSelectId = checkedId;
75 | check();
76 | } else { // 顺序
77 | sequenceSelectId = checkedId;
78 | }
79 | }
80 |
81 | @Override
82 | public int getContentView() {
83 | return R.layout.dialog_zfile_sort;
84 | }
85 |
86 | @NonNull
87 | @Override
88 | public Dialog createDialog(@Nullable Bundle bundle) {
89 | Dialog dialog = new Dialog(getContext(), R.style.ZFile_Common_Dialog);
90 | Window window = dialog.getWindow();
91 | if (window != null) {
92 | window.setGravity(Gravity.CENTER);
93 | }
94 | return dialog;
95 | }
96 |
97 | @Override
98 | public void init(@Nullable Bundle bundle) {
99 | if (getArguments() != null) {
100 | sortSelectId = getArguments().getInt("sortSelectId", 0);
101 | sequenceSelectId = getArguments().getInt("sequenceSelectId", 0);
102 | }
103 | initView();
104 | check();
105 | if (sortSelectId == R.id.zfile_sort_by_default) {
106 | zfileSortByDefault.setChecked(true);
107 | } else if (sortSelectId == R.id.zfile_sort_by_name) {
108 | zfileSortByName.setChecked(true);
109 | } else if (sortSelectId == R.id.zfile_sort_by_date) {
110 | zfileSortByDate.setChecked(true);
111 | } else if (sortSelectId == R.id.zfile_sort_by_size) {
112 | zfileSortBySize.setChecked(true);
113 | } else {
114 | zfileSortByDefault.setChecked(true);
115 | }
116 | if(sequenceSelectId == R.id.zfile_sequence_asc) {
117 | zfileSequenceAsc.setChecked(true);
118 | }else if(sequenceSelectId == R.id.zfile_sequence_desc) {
119 | zfileSequenceDesc.setChecked(true);
120 | }else {
121 | zfileSequenceAsc.setChecked(true);
122 | }
123 | zfileSortGroup.setOnCheckedChangeListener(this);
124 | zfileSequenceGroup.setOnCheckedChangeListener(this);
125 | zfileDialogSortCancel.setOnClickListener(new View.OnClickListener() {
126 | @Override
127 | public void onClick(View v) {
128 | dismiss();
129 | }
130 | });
131 | zfileDialogSortDown.setOnClickListener(new View.OnClickListener() {
132 | @Override
133 | public void onClick(View v) {
134 | if(checkedChangedListener != null) {
135 | checkedChangedListener.onCheckedChange(sortSelectId, sequenceSelectId);
136 | }
137 | dismiss();
138 | }
139 | });
140 | }
141 |
142 | private void check() {
143 | zfileSequenceLayout.setVisibility((sortSelectId == R.id.zfile_sort_by_default) ? View.GONE : View.VISIBLE);
144 | }
145 |
146 | @Override
147 | public void onStart() {
148 | super.onStart();
149 | ZFileContent.setNeedWH(this);
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/filePicker/src/main/java/com/kathline/library/ui/player/ScaleTextureView.java:
--------------------------------------------------------------------------------
1 | package com.kathline.library.ui.player;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.MotionEvent;
6 | import android.view.TextureView;
7 |
8 |
9 | /**
10 | * 带手势缩放的 TextureView 支持双指缩放、平移、旋转
11 | *
12 | * 同时也可以换成继承 RelativeLayout 或其它View,这样内部包含的view也会一起跟着变化
13 | *
14 | */
15 | public class ScaleTextureView extends TextureView {
16 |
17 | /**
18 | * 移动X
19 | */
20 | private float translationX;
21 | /**
22 | * 移动Y
23 | */
24 | private float translationY;
25 | /**
26 | * 伸缩比例
27 | */
28 | private float scale = 1;
29 | /**
30 | * 旋转角度
31 | */
32 | private float rotation;
33 |
34 | // 移动过程中临时变量
35 | private float actionX;
36 | private float actionY;
37 | private float spacing;
38 | private float degree;
39 | private int moveType; // 0=未选择,1=拖动,2=缩放
40 |
41 | /**
42 | * 默认开启缩放 Touch
43 | */
44 | private boolean enabledTouch = true;
45 |
46 | //默认启用旋转功能
47 | private boolean enabledRotation = true;
48 |
49 | //默认启用移动功能
50 | private boolean enabledTranslation = true;
51 |
52 | public ScaleTextureView(Context context) {
53 | this(context, null);
54 | }
55 |
56 | public ScaleTextureView(Context context, AttributeSet attrs) {
57 | this(context, attrs, 0);
58 | }
59 |
60 | public ScaleTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
61 | super(context, attrs, defStyleAttr);
62 | setClickable(true);
63 | }
64 |
65 | // @Override
66 | // public boolean onInterceptTouchEvent(MotionEvent ev) {
67 | // getParent().requestDisallowInterceptTouchEvent(true);
68 | // return super.onInterceptTouchEvent(ev);
69 | // }
70 |
71 | @Override
72 | public boolean onTouchEvent(MotionEvent event) {
73 | if (this.enabledTouch) {//整体 Touch 开关
74 | switch (event.getAction() & MotionEvent.ACTION_MASK) {
75 | case MotionEvent.ACTION_DOWN:
76 | moveType = 1;
77 | actionX = event.getRawX();
78 | actionY = event.getRawY();
79 | break;
80 | case MotionEvent.ACTION_POINTER_DOWN:
81 | moveType = 2;
82 | spacing = getSpacing(event);
83 | degree = getDegree(event);
84 | break;
85 | case MotionEvent.ACTION_MOVE:
86 | if (moveType == 1) {
87 | if (enabledTranslation) {//启用了移动操作
88 | translationX = translationX + event.getRawX() - actionX;
89 | translationY = translationY + event.getRawY() - actionY;
90 | setTranslationX(translationX);
91 | setTranslationY(translationY);
92 | actionX = event.getRawX();
93 | actionY = event.getRawY();
94 | }
95 | } else if (moveType == 2) {
96 | //启用缩放操作
97 | scale = scale * getSpacing(event) / spacing;
98 | setScaleX(scale);
99 | setScaleY(scale);
100 | if (enabledRotation) {//启用了旋转操作
101 | rotation = rotation + getDegree(event) - degree;
102 | if (rotation > 360) {
103 | rotation = rotation - 360;
104 | }
105 | if (rotation < -360) {
106 | rotation = rotation + 360;
107 | }
108 | setRotation(rotation);
109 | }
110 | }
111 | break;
112 | case MotionEvent.ACTION_UP:
113 | case MotionEvent.ACTION_POINTER_UP:
114 | moveType = 0;
115 | break;
116 | }
117 | }
118 | return super.onTouchEvent(event);
119 | }
120 |
121 | /**
122 | * 触碰两点间距离, 通过三角函数得到两点间的距离
123 | */
124 | private float getSpacing(MotionEvent event) {
125 | float x = event.getX(0) - event.getX(1);
126 | float y = event.getY(0) - event.getY(1);
127 | return (float) Math.sqrt(x * x + y * y);
128 | }
129 |
130 | /**
131 | * 取旋转角度. 得到两个手指间的旋转角度
132 | */
133 | private float getDegree(MotionEvent event) {
134 | double delta_x = event.getX(0) - event.getX(1);
135 | double delta_y = event.getY(0) - event.getY(1);
136 | double radians = Math.atan2(delta_y, delta_x);
137 | return (float) Math.toDegrees(radians);
138 | }
139 |
140 | /**
141 | * 整体手势开关设置
142 | * 设置是否启用 Touch true:启用(默认);false:禁用
143 | */
144 | public void setEnabledTouch(boolean enabled) {
145 | this.enabledTouch = enabled;
146 | }
147 |
148 | /**
149 | * 设置是否启用旋转功能 true:启用(默认);false:禁用
150 | */
151 | public void setEnabledRotation(boolean enabled) {
152 | this.enabledRotation = enabled;
153 | }
154 |
155 | /**
156 | * 设置是否启用移动功能 true:启用(默认);false:禁用
157 | */
158 | public void setEnabledTranslation(boolean enabled) {
159 | this.enabledTranslation = enabled;
160 | }
161 |
162 | /**
163 | * 重置为最原始默认状态
164 | *
165 | * @param saveEnabled 是否保留已经开启或禁用的 移动 旋转 缩放动作
166 | * true:保留;false:不保留
167 | */
168 | public void reset(boolean saveEnabled) {
169 | translationX = 0;
170 | translationY = 0;
171 | scale = 1;
172 | rotation = 0;
173 |
174 | // 移动过程中临时变量
175 | actionX = 0;
176 | actionY = 0;
177 | spacing = 0;
178 | degree = 0;
179 | moveType = 0;
180 |
181 | if (!saveEnabled) {
182 | //enabled 开关
183 | enabledTouch = true;
184 | enabledRotation = true;
185 | enabledTranslation = true;
186 | }
187 |
188 | setScaleX(1.0f);
189 | setScaleY(1.0f);
190 | setRotation(0);
191 | setTranslationX(0);
192 | setTranslationY(0);
193 | }
194 |
195 | }
196 |
--------------------------------------------------------------------------------