├── .gitignore
├── .idea
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── dictionaries
│ └── guxiuzhong.xml
├── encodings.xml
├── gradle.xml
├── kotlinc.xml
├── markdown-navigator.xml
├── markdown-navigator
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── gxz
│ │ └── example
│ │ └── videoedit
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── gxz
│ │ │ └── example
│ │ │ └── videoedit
│ │ │ ├── EditSpacingItemDecoration.java
│ │ │ ├── ExtractFrameWorkThread.java
│ │ │ ├── ExtractVideoInfoUtil.java
│ │ │ ├── MainActivity.java
│ │ │ ├── PictureUtils.java
│ │ │ ├── RangeSeekBar.java
│ │ │ ├── RangeSeekBar2.java
│ │ │ ├── UIUtil.java
│ │ │ ├── VideoEditActivity.java
│ │ │ ├── VideoEditAdapter.java
│ │ │ ├── VideoEditInfo.java
│ │ │ └── VideoExtractFrameAsyncUtils.java
│ └── res
│ │ ├── drawable-hdpi
│ │ ├── handle_left.png
│ │ ├── lf_ugc_publish_pos.xml
│ │ ├── upload_overlay_black.9.png
│ │ └── upload_overlay_trans.9.png
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── activity_video_edit.xml
│ │ └── video_item.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── gxz
│ └── example
│ └── videoedit
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── pic1.png
├── pic2.png
├── settings.gradle
└── sr-new.mp4
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/dictionaries/guxiuzhong.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator.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 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/misc.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 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | Abstraction issuesJava
46 |
47 |
48 | Android
49 |
50 |
51 | Android > Lint > Accessibility
52 |
53 |
54 | Android > Lint > Correctness
55 |
56 |
57 | Android > Lint > Correctness > Messages
58 |
59 |
60 | Android > Lint > Internationalization
61 |
62 |
63 | Android > Lint > Internationalization > Bidirectional Text
64 |
65 |
66 | Android > Lint > Performance
67 |
68 |
69 | Android > Lint > Security
70 |
71 |
72 | Android > Lint > Usability
73 |
74 |
75 | Android > Lint > Usability > Icons
76 |
77 |
78 | Android > Lint > Usability > Typography
79 |
80 |
81 | Android Lint for Kotlin
82 |
83 |
84 | Assignment issuesJava
85 |
86 |
87 | BashSupport
88 |
89 |
90 | Bitwise operation issuesJava
91 |
92 |
93 | C/C++
94 |
95 |
96 | Class metricsJava
97 |
98 |
99 | Class structureJava
100 |
101 |
102 | Cloning issuesJava
103 |
104 |
105 | Code maturity issuesJava
106 |
107 |
108 | Code style issuesJava
109 |
110 |
111 | Compiler issuesJava
112 |
113 |
114 | Concurrency annotation issuesJava
115 |
116 |
117 | Control FlowGroovy
118 |
119 |
120 | Control flow issuesJava
121 |
122 |
123 | Data flow issuesJava
124 |
125 |
126 | Dependency issuesJava
127 |
128 |
129 | Encapsulation issuesJava
130 |
131 |
132 | Error handlingJava
133 |
134 |
135 | Finalization issuesJava
136 |
137 |
138 | General
139 |
140 |
141 | GeneralC/C++
142 |
143 |
144 | GeneralJava
145 |
146 |
147 | Google Cloud Endpoints
148 |
149 |
150 | Gradle
151 |
152 |
153 | Groovy
154 |
155 |
156 | HTML
157 |
158 |
159 | ImportsJava
160 |
161 |
162 | Inheritance issuesJava
163 |
164 |
165 | Initialization issuesJava
166 |
167 |
168 | Internationalization issues
169 |
170 |
171 | Internationalization issuesJava
172 |
173 |
174 | J2ME issuesJava
175 |
176 |
177 | JUnit issuesJava
178 |
179 |
180 | Java
181 |
182 |
183 | Java language level issuesJava
184 |
185 |
186 | Java language level migration aidsJava
187 |
188 |
189 | JavaBeans issuesJava
190 |
191 |
192 | Javadoc issuesJava
193 |
194 |
195 | Kotlin
196 |
197 |
198 | Logging issuesJava
199 |
200 |
201 | Manifest
202 |
203 |
204 | Memory issuesJava
205 |
206 |
207 | Method MetricsGroovy
208 |
209 |
210 | Method metricsJava
211 |
212 |
213 | Modularization issuesJava
214 |
215 |
216 | Naming ConventionsGroovy
217 |
218 |
219 | Naming conventionsJava
220 |
221 |
222 | Numeric issuesJava
223 |
224 |
225 | Packaging issuesJava
226 |
227 |
228 | Pattern Validation
229 |
230 |
231 | Performance issuesJava
232 |
233 |
234 | Portability issuesJava
235 |
236 |
237 | Potentially confusing code constructsGroovy
238 |
239 |
240 | Probable bugsGradle
241 |
242 |
243 | Probable bugsGroovy
244 |
245 |
246 | Probable bugsJava
247 |
248 |
249 | Properties Files
250 |
251 |
252 | Properties FilesJava
253 |
254 |
255 | Python
256 |
257 |
258 | Resource management issuesJava
259 |
260 |
261 | Security issuesJava
262 |
263 |
264 | Serialization issuesJava
265 |
266 |
267 | StyleGroovy
268 |
269 |
270 | TestNGJava
271 |
272 |
273 | Threading issuesGroovy
274 |
275 |
276 | Threading issuesJava
277 |
278 |
279 | Visibility issuesJava
280 |
281 |
282 | XML
283 |
284 |
285 | toString() issuesJava
286 |
287 |
288 |
289 |
290 | Android
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 | $USER_HOME$/.subversion
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 | 1.8
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 高仿微信视频编辑页
2 | android视频编辑页面-高仿微信视频编辑页
3 |
4 | # 效果图
5 |
6 |
7 |
8 |
9 | # 介绍
10 | http://blog.csdn.net/ta893115871/article/details/70188162
11 |
12 | http://www.jianshu.com/p/55dcb62ca0b3
13 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.1"
6 | defaultConfig {
7 | applicationId "com.gxz.example.videoedit"
8 | minSdkVersion 15
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:25.3.1'
28 | compile 'com.android.support:design:25.3.1'
29 |
30 | compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha9'
31 | testCompile 'junit:junit:4.12'
32 | compile 'com.github.bumptech.glide:glide:3.7.0'
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/app/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/guxiuzhong/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 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/gxz/example/videoedit/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.gxz.example.videoedit;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.gxz.example.videoedit", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/gxz/example/videoedit/EditSpacingItemDecoration.java:
--------------------------------------------------------------------------------
1 | package com.gxz.example.videoedit;
2 |
3 | import android.graphics.Rect;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.View;
6 |
7 | /**
8 | * ================================================
9 | * 作 者:顾修忠-guxiuzhong@youku.com/gfj19900401@163.com
10 | * 版 本:
11 | * 创建日期:2017/2/18-上午1:03
12 | * 描 述:
13 | * 修订历史:
14 | * ================================================
15 | */
16 | public class EditSpacingItemDecoration extends RecyclerView.ItemDecoration {
17 |
18 | private int space;
19 | private int thumbnailsCount;
20 |
21 | public EditSpacingItemDecoration(int space, int thumbnailsCount) {
22 | this.space = space;
23 | this.thumbnailsCount = thumbnailsCount;
24 | }
25 |
26 | @Override
27 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
28 | // 第一个的前面和最后一个的后面
29 | int position = parent.getChildAdapterPosition(view);
30 | if (position == 0) {
31 | outRect.left = space;
32 | outRect.right = 0;
33 | } else if (thumbnailsCount > 10 && position == thumbnailsCount - 1) {
34 | outRect.left = 0;
35 | outRect.right = space;
36 | } else {
37 | outRect.left = 0;
38 | outRect.right = 0;
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/gxz/example/videoedit/ExtractFrameWorkThread.java:
--------------------------------------------------------------------------------
1 | package com.gxz.example.videoedit;
2 |
3 | import android.os.Handler;
4 |
5 | /**
6 | * ================================================
7 | * 作 者:顾修忠-guxiuzhong@youku.com/gfj19900401@163.com
8 | * 版 本:
9 | * 创建日期:2017/3/2-下午7:53
10 | * 描 述:
11 | * 修订历史:
12 | * ================================================
13 | */
14 |
15 | public class ExtractFrameWorkThread extends Thread {
16 | public static final int MSG_SAVE_SUCCESS = 0;
17 | private String videoPath;
18 | private String OutPutFileDirPath;
19 | private long startPosition;
20 | private long endPosition;
21 | private int thumbnailsCount;
22 | private VideoExtractFrameAsyncUtils mVideoExtractFrameAsyncUtils;
23 |
24 | public ExtractFrameWorkThread(int extractW, int extractH, Handler mHandler, String videoPath, String OutPutFileDirPath,
25 | long startPosition, long endPosition, int thumbnailsCount) {
26 | this.videoPath = videoPath;
27 | this.OutPutFileDirPath = OutPutFileDirPath;
28 | this.startPosition = startPosition;
29 | this.endPosition = endPosition;
30 | this.thumbnailsCount = thumbnailsCount;
31 | this.mVideoExtractFrameAsyncUtils = new VideoExtractFrameAsyncUtils(extractW,extractH,mHandler);
32 | }
33 |
34 | @Override
35 | public void run() {
36 | super.run();
37 | mVideoExtractFrameAsyncUtils.getVideoThumbnailsInfoForEdit(
38 | videoPath,
39 | OutPutFileDirPath,
40 | startPosition,
41 | endPosition,
42 | thumbnailsCount);
43 | }
44 |
45 | public void stopExtract() {
46 | if (mVideoExtractFrameAsyncUtils != null) {
47 | mVideoExtractFrameAsyncUtils.stopExtract();
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/gxz/example/videoedit/ExtractVideoInfoUtil.java:
--------------------------------------------------------------------------------
1 | package com.gxz.example.videoedit;
2 |
3 | import android.graphics.Bitmap;
4 | import android.media.MediaMetadataRetriever;
5 | import android.os.Build;
6 | import android.text.TextUtils;
7 |
8 | import java.io.File;
9 |
10 | /**
11 | * ================================================
12 | * 作 者:顾修忠-guxiuzhong@youku.com/gfj19900401@163.com
13 | * 版 本:
14 | * 创建日期:2017/2/23-上午11:05
15 | * 描 述:
16 | * 修订历史:
17 | * ================================================
18 | */
19 |
20 | public class ExtractVideoInfoUtil {
21 | private MediaMetadataRetriever mMetadataRetriever;
22 | private long fileLength = 0;//毫秒
23 |
24 | public ExtractVideoInfoUtil(String path) {
25 | if (TextUtils.isEmpty(path)) {
26 | throw new RuntimeException("path must be not null !");
27 | }
28 | File file = new File(path);
29 | if (!file.exists()) {
30 | throw new RuntimeException("path file not exists !");
31 | }
32 | mMetadataRetriever = new MediaMetadataRetriever();
33 | mMetadataRetriever.setDataSource(file.getAbsolutePath());
34 | String len = getVideoLength();
35 | fileLength = TextUtils.isEmpty(len) ? 0 : Long.valueOf(len);
36 |
37 | }
38 |
39 | public int getVideoWidth() {
40 | String w = mMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
41 | int width = -1;
42 | if (!TextUtils.isEmpty(w)) {
43 | width = Integer.valueOf(w);
44 | }
45 | return width;
46 | }
47 |
48 | public int getVideoHeight() {
49 | String h = mMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
50 | int height = -1;
51 | if (!TextUtils.isEmpty(h)) {
52 | height = Integer.valueOf(h);
53 | }
54 | return height;
55 | }
56 |
57 | /**
58 | * 获取视频的典型的一帧图片,不耗时
59 | *
60 | * @return Bitmap
61 | */
62 | public Bitmap extractFrame() {
63 | return mMetadataRetriever.getFrameAtTime();
64 | }
65 |
66 | /**
67 | * 获取视频某一帧,不一定是关键帧
68 | *
69 | * @param timeMs 毫秒
70 | */
71 | public Bitmap extractFrame(long timeMs) {
72 | //第一个参数是传入时间,只能是us(微秒)
73 | //OPTION_CLOSEST ,在给定的时间,检索最近一个帧,这个帧不一定是关键帧。
74 | //OPTION_CLOSEST_SYNC 在给定的时间,检索最近一个同步与数据源相关联的的帧(关键帧)
75 | //OPTION_NEXT_SYNC 在给定时间之后检索一个同步与数据源相关联的关键帧。
76 | //OPTION_PREVIOUS_SYNC 顾名思义,同上
77 | // Bitmap bitmap = mMetadataRetriever.getFrameAtTime(timeMs * 1000, MediaMetadataRetriever.OPTION_CLOSEST);
78 | Bitmap bitmap = null;
79 | for (long i = timeMs; i < fileLength; i += 1000) {
80 | bitmap = mMetadataRetriever.getFrameAtTime(i * 1000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
81 | if (bitmap != null) {
82 | break;
83 | }
84 | }
85 | return bitmap;
86 | }
87 |
88 |
89 |
90 | /***
91 | * 获取视频的长度时间
92 | *
93 | * @return String 毫秒
94 | */
95 | public String getVideoLength() {
96 | return mMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
97 | }
98 |
99 | /**
100 | * 获取视频旋转角度
101 | *
102 | * @return
103 | */
104 | public int getVideoDegree() {
105 | int degree = 0;
106 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
107 | String degreeStr = mMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
108 | if (!TextUtils.isEmpty(degreeStr)) {
109 | degree = Integer.valueOf(degreeStr);
110 | }
111 | }
112 | return degree;
113 | }
114 |
115 | public void release() {
116 | if (mMetadataRetriever != null) {
117 | mMetadataRetriever.release();
118 | }
119 | }
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/app/src/main/java/com/gxz/example/videoedit/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.gxz.example.videoedit;
2 |
3 | import android.content.Intent;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.os.Bundle;
6 | import android.view.View;
7 |
8 | public class MainActivity extends AppCompatActivity {
9 |
10 | @Override
11 | protected void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setContentView(R.layout.activity_main);
14 | }
15 | public void videoEdit(View view){
16 | startActivity(new Intent(this,VideoEditActivity.class));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/gxz/example/videoedit/PictureUtils.java:
--------------------------------------------------------------------------------
1 | package com.gxz.example.videoedit;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.os.Environment;
6 |
7 | import java.io.File;
8 | import java.io.FileOutputStream;
9 | import java.io.IOException;
10 |
11 | /**
12 | * ================================================
13 | * 作 者:顾修忠-guxiuzhong@youku.com/gfj19900401@163.com
14 | * 版 本:
15 | * 创建日期:2017/4/4-下午6:56
16 | * 描 述:
17 | * 修订历史:
18 | * ================================================
19 | */
20 |
21 | public class PictureUtils {
22 | public static final String POSTFIX = ".jpeg";
23 | private static final String EDIT_PATH = "/EditVideo/";
24 |
25 | public static String saveImageToSD(Bitmap bmp, String dirPath) {
26 | if (bmp == null) {
27 | return "";
28 | }
29 | File appDir = new File(dirPath);
30 | if (!appDir.exists()) {
31 | appDir.mkdir();
32 | }
33 | String fileName = System.currentTimeMillis() + ".jpg";
34 | File file = new File(appDir, fileName);
35 | try {
36 | FileOutputStream fos = new FileOutputStream(file);
37 | bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);
38 | fos.flush();
39 | fos.close();
40 | } catch (IOException e) {
41 | e.printStackTrace();
42 | }
43 | return file.getAbsolutePath();
44 | }
45 |
46 |
47 |
48 | public static String saveImageToSDForEdit(Bitmap bmp, String dirPath, String fileName) {
49 | if (bmp == null) {
50 | return "";
51 | }
52 | File appDir = new File(dirPath);
53 | if (!appDir.exists()) {
54 | appDir.mkdir();
55 | }
56 | File file = new File(appDir, fileName);
57 | try {
58 | FileOutputStream fos = new FileOutputStream(file);
59 | bmp.compress(Bitmap.CompressFormat.JPEG, 80, fos);
60 | fos.flush();
61 | fos.close();
62 | } catch (IOException e) {
63 | e.printStackTrace();
64 | }
65 | return file.getAbsolutePath();
66 | }
67 |
68 | public static void deleteFile(File f) {
69 | if (f.isDirectory()) {
70 | File[] files = f.listFiles();
71 | if (files != null && files.length > 0) {
72 | for (int i = 0; i < files.length; ++i) {
73 | deleteFile(files[i]);
74 | }
75 | }
76 | }
77 | f.delete();
78 | }
79 |
80 | public static String getSaveEditThumbnailDir(Context context) {
81 | String state = Environment.getExternalStorageState();
82 | File rootDir = state.equals(Environment.MEDIA_MOUNTED) ? Environment.getExternalStorageDirectory() : context.getCacheDir();
83 | File folderDir = new File(rootDir.getAbsolutePath() + EDIT_PATH);
84 | if (!folderDir.exists() && folderDir.mkdirs()) {
85 |
86 | }
87 | return folderDir.getAbsolutePath();
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/gxz/example/videoedit/RangeSeekBar.java:
--------------------------------------------------------------------------------
1 | package com.gxz.example.videoedit;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapFactory;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Matrix;
9 | import android.graphics.Paint;
10 | import android.os.Bundle;
11 | import android.os.Parcelable;
12 | import android.support.annotation.Nullable;
13 | import android.util.AttributeSet;
14 | import android.util.Log;
15 | import android.view.MotionEvent;
16 | import android.view.View;
17 | import android.view.ViewConfiguration;
18 |
19 | import java.text.DecimalFormat;
20 |
21 |
22 | /**
23 | * ================================================
24 | * 作 者:顾修忠-guxiuzhong@youku.com/gfj19900401@163.com
25 | * 版 本:
26 | * 创建日期:2017/4/4-下午1:22
27 | * 描 述:
28 | * 修订历史:
29 | * ================================================
30 | */
31 |
32 | public class RangeSeekBar extends View {
33 | private static final String TAG = RangeSeekBar.class.getSimpleName();
34 | private double absoluteMinValuePrim, absoluteMaxValuePrim;
35 | private double normalizedMinValue = 0d;//点坐标占总长度的比例值,范围从0-1
36 | private double normalizedMaxValue = 1d;//点坐标占总长度的比例值,范围从0-1
37 | private long min_cut_time = 3000;
38 | private double normalizedMinValueTime = 0d;
39 | private double normalizedMaxValueTime = 1d;// normalized:规格化的--点坐标占总长度的比例值,范围从0-1
40 | private int mScaledTouchSlop;
41 | private Bitmap thumbImageLeft;
42 | private Bitmap thumbImageRight;
43 | private Bitmap thumbPressedImage;
44 | private Bitmap mBitmapBlack;
45 | private Bitmap mBitmapPro;
46 | private Paint paint;
47 | private Paint rectPaint;
48 | private int thumbWidth;
49 | private float thumbHalfWidth;
50 | private final float padding = 0;
51 |
52 | private float thumbPaddingTop = 0;
53 | private float thumbPressPaddingTop = 0;
54 | private boolean isTouchDown;
55 | public static final int INVALID_POINTER_ID = 255;
56 | public static final int ACTION_POINTER_INDEX_MASK = 0x0000ff00, ACTION_POINTER_INDEX_SHIFT = 8;
57 | private int mActivePointerId = INVALID_POINTER_ID;
58 | private float mDownMotionX;
59 | private boolean mIsDragging;
60 | private Thumb pressedThumb;
61 | private boolean isMin;
62 | private double min_width = 1;//最小裁剪距离
63 | private boolean notifyWhileDragging = false;
64 |
65 | public enum Thumb {
66 | MIN, MAX
67 | }
68 |
69 | public RangeSeekBar(Context context) {
70 | super(context);
71 | }
72 |
73 | public RangeSeekBar(Context context, @Nullable AttributeSet attrs) {
74 | super(context, attrs);
75 | }
76 |
77 | public RangeSeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
78 | super(context, attrs, defStyleAttr);
79 | }
80 |
81 | public RangeSeekBar(Context context, long absoluteMinValuePrim, long absoluteMaxValuePrim) {
82 | super(context);
83 | this.absoluteMinValuePrim = absoluteMinValuePrim;
84 | this.absoluteMaxValuePrim = absoluteMaxValuePrim;
85 | setFocusable(true);
86 | setFocusableInTouchMode(true);
87 | init();
88 | }
89 |
90 | private void init() {
91 | mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
92 | //等比例缩放图片
93 | thumbImageLeft = BitmapFactory.decodeResource(getResources(), R.drawable.handle_left);
94 | int width = thumbImageLeft.getWidth();
95 | int height = thumbImageLeft.getHeight();
96 | int newWidth = dip2px(11);
97 | int newHeight = dip2px(55);
98 | float scaleWidth = newWidth * 1.0f / width;
99 | float scaleHeight = newHeight * 1.0f / height;
100 | Matrix matrix = new Matrix();
101 | matrix.postScale(scaleWidth, scaleHeight);
102 | thumbImageLeft = Bitmap.createBitmap(thumbImageLeft, 0, 0, width, height, matrix, true);
103 | thumbImageRight = thumbImageLeft;
104 | thumbPressedImage = thumbImageLeft;
105 | thumbWidth = newWidth;
106 | thumbHalfWidth = thumbWidth / 2;
107 |
108 |
109 | mBitmapBlack = BitmapFactory.decodeResource(getResources(), R.drawable.upload_overlay_black);
110 | mBitmapPro = BitmapFactory.decodeResource(getResources(), R.drawable.upload_overlay_trans);
111 | paint = new Paint(Paint.ANTI_ALIAS_FLAG);
112 | rectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
113 | rectPaint.setStyle(Paint.Style.FILL);
114 | rectPaint.setColor(Color.parseColor("#ffffff"));
115 | }
116 |
117 | @Override
118 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
119 | int width = 300;
120 | if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) {
121 | width = MeasureSpec.getSize(widthMeasureSpec);
122 | }
123 | int height = 120;
124 | if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) {
125 | height = MeasureSpec.getSize(heightMeasureSpec);
126 | }
127 | setMeasuredDimension(width, height);
128 | }
129 |
130 | @Override
131 | protected void onDraw(Canvas canvas) {
132 | super.onDraw(canvas);
133 | float bg_middle_left = 0;
134 | float bg_middle_right = getWidth() - getPaddingRight();
135 | float scale = (bg_middle_right - bg_middle_left) / mBitmapPro.getWidth();
136 |
137 | float rangeL = normalizedToScreen(normalizedMinValue);
138 | float rangeR = normalizedToScreen(normalizedMaxValue);
139 | float scale_pro = (rangeR - rangeL) / mBitmapPro.getWidth();
140 | if (scale_pro > 0) {
141 | try {
142 | Matrix pro_mx = new Matrix();
143 | pro_mx.postScale(scale_pro, 1f);
144 | Bitmap m_bitmap_pro_new = Bitmap.createBitmap(mBitmapPro, 0, 0, mBitmapPro.getWidth(),
145 | mBitmapPro.getHeight(), pro_mx, true);
146 |
147 | //画中间的透明遮罩
148 | canvas.drawBitmap(m_bitmap_pro_new, rangeL, thumbPaddingTop, paint);
149 |
150 | Matrix mx = new Matrix();
151 | mx.postScale(scale, 1f);
152 | Bitmap m_bitmap_black_new = Bitmap.createBitmap(mBitmapBlack, 0, 0, mBitmapBlack.getWidth(), mBitmapBlack.getHeight(), mx, true);
153 |
154 | //画左边的半透明遮罩
155 | Bitmap m_bg_new1 = Bitmap.createBitmap(m_bitmap_black_new, 0, 0, (int) (rangeL - bg_middle_left) + (int) thumbWidth / 2, mBitmapBlack.getHeight());
156 | canvas.drawBitmap(m_bg_new1, bg_middle_left, thumbPaddingTop, paint);
157 |
158 | //画右边的半透明遮罩
159 | Bitmap m_bg_new2 = Bitmap.createBitmap(m_bitmap_black_new, (int) (rangeR - thumbWidth / 2), 0, (int) (getWidth() - rangeR) + (int) thumbWidth / 2, mBitmapBlack.getHeight());
160 | canvas.drawBitmap(m_bg_new2, (int) (rangeR - thumbWidth / 2), thumbPaddingTop, paint);
161 |
162 | //画上下的矩形
163 | canvas.drawRect(rangeL, thumbPaddingTop, rangeR, thumbPaddingTop + dip2px(2), rectPaint);
164 | canvas.drawRect(rangeL, getHeight() - dip2px(2), rangeR, getHeight(), rectPaint);
165 | //画左右thumb
166 | drawThumb(normalizedToScreen(normalizedMinValue), false, canvas, true);
167 | drawThumb(normalizedToScreen(normalizedMaxValue), false, canvas, false);
168 | } catch (Exception e) {
169 | // 当pro_scale非常小,例如width=12,Height=48,pro_scale=0.01979065时,
170 | // 宽高按比例计算后值为0.237、0.949,系统强转为int型后宽就变成0了。就出现非法参数异常
171 | Log.e(TAG,
172 | "IllegalArgumentException--width=" + mBitmapPro.getWidth() + "Height=" + mBitmapPro.getHeight()
173 | + "scale_pro=" + scale_pro, e);
174 | }
175 | }
176 | }
177 |
178 |
179 | private void drawThumb(float screenCoord, boolean pressed, Canvas canvas, boolean isLeft) {
180 | canvas.drawBitmap(pressed ? thumbPressedImage : (isLeft ? thumbImageLeft : thumbImageRight), screenCoord - (isLeft ? 0 : thumbWidth), (pressed ? thumbPressPaddingTop : thumbPaddingTop), paint);
181 | }
182 |
183 |
184 | @Override
185 | public boolean onTouchEvent(MotionEvent event) {
186 | if (isTouchDown) {
187 | return super.onTouchEvent(event);
188 | }
189 | if (event.getPointerCount() > 1) {
190 | return super.onTouchEvent(event);
191 | }
192 |
193 | if (!isEnabled())
194 | return false;
195 | if (absoluteMaxValuePrim <= min_cut_time) {
196 | return super.onTouchEvent(event);
197 | }
198 | int pointerIndex;// 记录点击点的index
199 | final int action = event.getAction();
200 | switch (action & MotionEvent.ACTION_MASK) {
201 | case MotionEvent.ACTION_DOWN:
202 | //记住最后一个手指点击屏幕的点的坐标x,mDownMotionX
203 | mActivePointerId = event.getPointerId(event.getPointerCount() - 1);
204 | pointerIndex = event.findPointerIndex(mActivePointerId);
205 | mDownMotionX = event.getX(pointerIndex);
206 | // 判断touch到的是最大值thumb还是最小值thumb
207 | pressedThumb = evalPressedThumb(mDownMotionX);
208 | if (pressedThumb == null)
209 | return super.onTouchEvent(event);
210 | setPressed(true);// 设置该控件被按下了
211 | onStartTrackingTouch();// 置mIsDragging为true,开始追踪touch事件
212 | trackTouchEvent(event);
213 | attemptClaimDrag();
214 | if (listener != null) {
215 | listener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue(), MotionEvent.ACTION_DOWN, isMin, pressedThumb);
216 | }
217 | break;
218 | case MotionEvent.ACTION_MOVE:
219 | if (pressedThumb != null) {
220 | if (mIsDragging) {
221 | trackTouchEvent(event);
222 | } else {
223 | // Scroll to follow the motion event
224 | pointerIndex = event.findPointerIndex(mActivePointerId);
225 | final float x = event.getX(pointerIndex);// 手指在控件上点的X坐标
226 | // 手指没有点在最大最小值上,并且在控件上有滑动事件
227 | if (Math.abs(x - mDownMotionX) > mScaledTouchSlop) {
228 | setPressed(true);
229 | Log.e(TAG, "没有拖住最大最小值");// 一直不会执行?
230 | invalidate();
231 | onStartTrackingTouch();
232 | trackTouchEvent(event);
233 | attemptClaimDrag();
234 | }
235 | }
236 | if (notifyWhileDragging && listener != null) {
237 | listener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue(), MotionEvent.ACTION_MOVE, isMin, pressedThumb);
238 | }
239 | }
240 | break;
241 | case MotionEvent.ACTION_UP:
242 | if (mIsDragging) {
243 | trackTouchEvent(event);
244 | onStopTrackingTouch();
245 | setPressed(false);
246 | } else {
247 | onStartTrackingTouch();
248 | trackTouchEvent(event);
249 | onStopTrackingTouch();
250 | }
251 |
252 | invalidate();
253 | if (listener != null) {
254 | listener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue(), MotionEvent.ACTION_UP, isMin, pressedThumb);
255 | }
256 | pressedThumb = null;// 手指抬起,则置被touch到的thumb为空
257 | break;
258 | case MotionEvent.ACTION_POINTER_DOWN:
259 | final int index = event.getPointerCount() - 1;
260 | // final int index = ev.getActionIndex();
261 | mDownMotionX = event.getX(index);
262 | mActivePointerId = event.getPointerId(index);
263 | invalidate();
264 | break;
265 | case MotionEvent.ACTION_POINTER_UP:
266 | onSecondaryPointerUp(event);
267 | invalidate();
268 | break;
269 | case MotionEvent.ACTION_CANCEL:
270 | if (mIsDragging) {
271 | onStopTrackingTouch();
272 | setPressed(false);
273 | }
274 | invalidate(); // see above explanation
275 | break;
276 | default:
277 | break;
278 | }
279 | return true;
280 | }
281 |
282 | private void onSecondaryPointerUp(MotionEvent ev) {
283 | final int pointerIndex = (ev.getAction() & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;
284 |
285 | final int pointerId = ev.getPointerId(pointerIndex);
286 | if (pointerId == mActivePointerId) {
287 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
288 | mDownMotionX = ev.getX(newPointerIndex);
289 | mActivePointerId = ev.getPointerId(newPointerIndex);
290 | }
291 | }
292 |
293 | private void trackTouchEvent(MotionEvent event) {
294 | if (event.getPointerCount() > 1) return;
295 | Log.e(TAG, "trackTouchEvent: " + event.getAction() + " x: " + event.getX());
296 | final int pointerIndex = event.findPointerIndex(mActivePointerId);// 得到按下点的index
297 | float x = 0;
298 | try {
299 | x = event.getX(pointerIndex);
300 | } catch (Exception e) {
301 | return;
302 | }
303 | if (Thumb.MIN.equals(pressedThumb)) {
304 | // screenToNormalized(x)-->得到规格化的0-1的值
305 | setNormalizedMinValue(screenToNormalized(x, 0));
306 | } else if (Thumb.MAX.equals(pressedThumb)) {
307 | setNormalizedMaxValue(screenToNormalized(x, 1));
308 | }
309 | }
310 |
311 | private double screenToNormalized(float screenCoord, int position) {
312 | int width = getWidth();
313 | if (width <= 2 * padding) {
314 | // prevent division by zero, simply return 0.
315 | return 0d;
316 | } else {
317 | isMin = false;
318 | double current_width = screenCoord;
319 | float rangeL = normalizedToScreen(normalizedMinValue);
320 | float rangeR = normalizedToScreen(normalizedMaxValue);
321 | double min = min_cut_time / (absoluteMaxValuePrim - absoluteMinValuePrim) * (width - thumbWidth * 2);
322 |
323 | if (absoluteMaxValuePrim > 5 * 60 * 1000) {//大于5分钟的精确小数四位
324 | DecimalFormat df = new DecimalFormat("0.0000");
325 | min_width = Double.parseDouble(df.format(min));
326 | } else {
327 | min_width = Math.round(min + 0.5d);
328 | }
329 | if (position == 0) {
330 | if (isInThumbRangeLeft(screenCoord, normalizedMinValue, 0.5)) {
331 | return normalizedMinValue;
332 | }
333 |
334 | float rightPosition = (getWidth() - rangeR) >= 0 ? (getWidth() - rangeR) : 0;
335 | double left_length = getValueLength() - (rightPosition + min_width);
336 |
337 |
338 | if (current_width > rangeL) {
339 | current_width = rangeL + (current_width - rangeL);
340 | } else if (current_width <= rangeL) {
341 | current_width = rangeL - (rangeL - current_width);
342 | }
343 |
344 | if (current_width > left_length) {
345 | isMin = true;
346 | current_width = left_length;
347 | }
348 |
349 | if (current_width < thumbWidth * 2 / 3) {
350 | current_width = 0;
351 | }
352 |
353 | double resultTime = (current_width - padding) / (width - 2 * thumbWidth);
354 | normalizedMinValueTime = Math.min(1d, Math.max(0d, resultTime));
355 | double result = (current_width - padding) / (width - 2 * padding);
356 | return Math.min(1d, Math.max(0d, result));// 保证该该值为0-1之间,但是什么时候这个判断有用呢?
357 | } else {
358 | if (isInThumbRange(screenCoord, normalizedMaxValue, 0.5)) {
359 | return normalizedMaxValue;
360 | }
361 |
362 | double right_length = getValueLength() - (rangeL + min_width);
363 | if (current_width > rangeR) {
364 | current_width = rangeR + (current_width - rangeR);
365 | } else if (current_width <= rangeR) {
366 | current_width = rangeR - (rangeR - current_width);
367 | }
368 |
369 | double paddingRight = getWidth() - current_width;
370 |
371 | if (paddingRight > right_length) {
372 | isMin = true;
373 | current_width = getWidth() - right_length;
374 | paddingRight = right_length;
375 | }
376 |
377 | if (paddingRight < thumbWidth * 2 / 3) {
378 | current_width = getWidth();
379 | paddingRight = 0;
380 | }
381 |
382 | double resultTime = (paddingRight - padding) / (width - 2 * thumbWidth);
383 | resultTime = 1 - resultTime;
384 | normalizedMaxValueTime = Math.min(1d, Math.max(0d, resultTime));
385 | double result = (current_width - padding) / (width - 2 * padding);
386 | return Math.min(1d, Math.max(0d, result));// 保证该该值为0-1之间,但是什么时候这个判断有用呢?
387 | }
388 |
389 | }
390 | }
391 |
392 | private int getValueLength() {
393 | return (getWidth() - 2 * thumbWidth);
394 | }
395 |
396 | /**
397 | * 计算位于哪个Thumb内
398 | *
399 | * @param touchX touchX
400 | * @return 被touch的是空还是最大值或最小值
401 | */
402 | private Thumb evalPressedThumb(float touchX) {
403 | Thumb result = null;
404 | boolean minThumbPressed = isInThumbRange(touchX, normalizedMinValue, 2);// 触摸点是否在最小值图片范围内
405 | boolean maxThumbPressed = isInThumbRange(touchX, normalizedMaxValue, 2);
406 | if (minThumbPressed && maxThumbPressed) {
407 | // 如果两个thumbs重叠在一起,无法判断拖动哪个,做以下处理
408 | // 触摸点在屏幕右侧,则判断为touch到了最小值thumb,反之判断为touch到了最大值thumb
409 | result = (touchX / getWidth() > 0.5f) ? Thumb.MIN : Thumb.MAX;
410 | } else if (minThumbPressed) {
411 | result = Thumb.MIN;
412 | } else if (maxThumbPressed) {
413 | result = Thumb.MAX;
414 | }
415 | return result;
416 | }
417 |
418 | private boolean isInThumbRange(float touchX, double normalizedThumbValue, double scale) {
419 | // 当前触摸点X坐标-最小值图片中心点在屏幕的X坐标之差<=最小点图片的宽度的一般
420 | // 即判断触摸点是否在以最小值图片中心为原点,宽度一半为半径的圆内。
421 | return Math.abs(touchX - normalizedToScreen(normalizedThumbValue)) <= thumbHalfWidth * scale;
422 | }
423 |
424 | private boolean isInThumbRangeLeft(float touchX, double normalizedThumbValue, double scale) {
425 | // 当前触摸点X坐标-最小值图片中心点在屏幕的X坐标之差<=最小点图片的宽度的一般
426 | // 即判断触摸点是否在以最小值图片中心为原点,宽度一半为半径的圆内。
427 | return Math.abs(touchX - normalizedToScreen(normalizedThumbValue) - thumbWidth) <= thumbHalfWidth * scale;
428 | }
429 |
430 | /**
431 | * 试图告诉父view不要拦截子控件的drag
432 | */
433 | private void attemptClaimDrag() {
434 | if (getParent() != null) {
435 | getParent().requestDisallowInterceptTouchEvent(true);
436 | }
437 | }
438 |
439 | void onStartTrackingTouch() {
440 | mIsDragging = true;
441 | }
442 |
443 |
444 | void onStopTrackingTouch() {
445 | mIsDragging = false;
446 | }
447 |
448 | public void setMin_cut_time(long min_cut_time) {
449 | this.min_cut_time = min_cut_time;
450 | }
451 |
452 |
453 | private float normalizedToScreen(double normalizedCoord) {
454 | return (float) (getPaddingLeft() + normalizedCoord * (getWidth() - getPaddingLeft() - getPaddingRight()));
455 | }
456 |
457 | private double valueToNormalized(long value) {
458 | if (0 == absoluteMaxValuePrim - absoluteMinValuePrim) {
459 | return 0d;
460 | }
461 | return (value - absoluteMinValuePrim) / (absoluteMaxValuePrim - absoluteMinValuePrim);
462 | }
463 |
464 | public void setSelectedMinValue(long value) {
465 | if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
466 | setNormalizedMinValue(0d);
467 | } else {
468 | setNormalizedMinValue(valueToNormalized(value));
469 | }
470 | }
471 |
472 | public void setSelectedMaxValue(long value) {
473 | if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
474 | setNormalizedMaxValue(1d);
475 | } else {
476 | setNormalizedMaxValue(valueToNormalized(value));
477 | }
478 | }
479 |
480 | public void setNormalizedMinValue(double value) {
481 | normalizedMinValue = Math.max(0d, Math.min(1d, Math.min(value, normalizedMaxValue)));
482 | invalidate();// 重新绘制此view
483 | }
484 |
485 |
486 | public void setNormalizedMaxValue(double value) {
487 | normalizedMaxValue = Math.max(0d, Math.min(1d, Math.max(value, normalizedMinValue)));
488 | invalidate();// 重新绘制此view
489 | }
490 |
491 |
492 | public long getSelectedMinValue() {
493 | return normalizedToValue(normalizedMinValueTime);
494 | }
495 |
496 | public long getSelectedMaxValue() {
497 | return normalizedToValue(normalizedMaxValueTime);
498 | }
499 |
500 | private long normalizedToValue(double normalized) {
501 | return (long) (absoluteMinValuePrim + normalized
502 | * (absoluteMaxValuePrim - absoluteMinValuePrim));
503 | }
504 |
505 | /**
506 | * 供外部activity调用,控制是都在拖动的时候打印log信息,默认是false不打印
507 | */
508 | public boolean isNotifyWhileDragging() {
509 | return notifyWhileDragging;
510 | }
511 |
512 |
513 | public void setNotifyWhileDragging(boolean flag) {
514 | this.notifyWhileDragging = flag;
515 | }
516 |
517 | public int dip2px(int dip) {
518 | float scale = getContext().getResources().getDisplayMetrics().density;
519 | return (int) ((float) dip * scale + 0.5F);
520 | }
521 |
522 | public void setTouchDown(boolean touchDown) {
523 | isTouchDown = touchDown;
524 | }
525 |
526 | @Override
527 | protected Parcelable onSaveInstanceState() {
528 | final Bundle bundle = new Bundle();
529 | bundle.putParcelable("SUPER", super.onSaveInstanceState());
530 | bundle.putDouble("MIN", normalizedMinValue);
531 | bundle.putDouble("MAX", normalizedMaxValue);
532 | bundle.putDouble("MIN_TIME", normalizedMinValueTime);
533 | bundle.putDouble("MAX_TIME", normalizedMaxValueTime);
534 | return bundle;
535 | }
536 |
537 | @Override
538 | protected void onRestoreInstanceState(Parcelable parcel) {
539 | final Bundle bundle = (Bundle) parcel;
540 | super.onRestoreInstanceState(bundle.getParcelable("SUPER"));
541 | normalizedMinValue = bundle.getDouble("MIN");
542 | normalizedMaxValue = bundle.getDouble("MAX");
543 | normalizedMinValueTime = bundle.getDouble("MIN_TIME");
544 | normalizedMaxValueTime = bundle.getDouble("MAX_TIME");
545 | }
546 |
547 | private OnRangeSeekBarChangeListener listener;
548 |
549 | public interface OnRangeSeekBarChangeListener {
550 | void onRangeSeekBarValuesChanged(RangeSeekBar bar, long minValue, long maxValue, int action, boolean isMin, Thumb pressedThumb);
551 | }
552 |
553 | public void setOnRangeSeekBarChangeListener(OnRangeSeekBarChangeListener listener) {
554 | this.listener = listener;
555 | }
556 | }
557 |
--------------------------------------------------------------------------------
/app/src/main/java/com/gxz/example/videoedit/RangeSeekBar2.java:
--------------------------------------------------------------------------------
1 | package com.gxz.example.videoedit;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapFactory;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Matrix;
9 | import android.graphics.Paint;
10 | import android.os.Bundle;
11 | import android.os.Parcelable;
12 | import android.text.TextUtils;
13 | import android.util.Log;
14 | import android.view.MotionEvent;
15 | import android.view.ViewConfiguration;
16 | import android.widget.ImageView;
17 |
18 | import java.io.File;
19 | import java.math.BigDecimal;
20 | import java.text.DecimalFormat;
21 |
22 | public class RangeSeekBar2 extends ImageView {
23 | private static final String TAG = RangeSeekBar.class.getSimpleName();
24 | private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
25 | private Paint rectPaint;
26 | private Bitmap thumbImage_left;
27 | private Bitmap thumbImage_right;
28 | private Bitmap thumbPressedImage;
29 | private int thumbWidth;
30 | private float thumbHalfWidth;
31 | private float thumbHalfHeight;
32 | private final float thumbPaddingTop = 0;
33 | private final float thumbPressPaddingTop = 0;
34 | private final float padding = 0;
35 | private final T absoluteMinValue, absoluteMaxValue;
36 | private final NumberType numberType;
37 | private final double absoluteMinValuePrim, absoluteMaxValuePrim;
38 | private double normalizedMinValue = 0d;
39 | private double normalizedMaxValue = 1d;// normalized:规格化的--点坐标占总长度的比例值,范围从0-1
40 | private double normalizedMinValueTime = 0d;
41 | private double normalizedMaxValueTime = 1d;// normalized:规格化的--点坐标占总长度的比例值,范围从0-1
42 | private Thumb pressedThumb = null;
43 | private boolean notifyWhileDragging = false;
44 | private OnRangeSeekBarChangeListener listener;
45 | private double min_width = 1;//最小裁剪距离
46 | private long min_cut_time = 2000;
47 | private boolean isMin;
48 | public static final int FILE_NOT_EXIST_ACTION = 144;
49 | public static final int INVALID_POINTER_ID = 255;
50 | public static final int ACTION_POINTER_INDEX_MASK = 0x0000ff00,
51 | ACTION_POINTER_INDEX_SHIFT = 8;
52 | private float mDownMotionX;// 记录touchEvent按下时的X坐标
53 | private int mActivePointerId = INVALID_POINTER_ID;
54 | private int mScaledTouchSlop;
55 | private boolean mIsDragging;
56 | private String videoPath;
57 | private boolean isTouchDown;
58 | private Bitmap m_bg;
59 | private Bitmap m_progress;
60 |
61 |
62 | public RangeSeekBar2(Context context,T absoluteMinValue, T absoluteMaxValue) throws IllegalArgumentException {
63 | super(context);
64 | this.absoluteMinValue = absoluteMinValue;
65 | this.absoluteMaxValue = absoluteMaxValue;
66 | absoluteMinValuePrim = absoluteMinValue.doubleValue();// 都转换为double类型的值
67 | absoluteMaxValuePrim = absoluteMaxValue.doubleValue();
68 | numberType = NumberType.fromNumber(absoluteMinValue);// 得到输入数字的枚举类型
69 | setFocusable(true);
70 | setFocusableInTouchMode(true);
71 | init();
72 | }
73 |
74 | private void init() {
75 | // 被认为是触摸滑动的最短距离
76 | mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
77 | thumbImage_left = BitmapFactory.decodeResource(getResources(), R.drawable.handle_left);
78 | int width = thumbImage_left.getWidth();
79 | int height = thumbImage_left.getHeight();
80 | int newWidth = UIUtil.dip2px(getContext(),11);
81 | int newHeight = UIUtil.dip2px(getContext(),55);
82 | float scaleWidth = newWidth*1.0f/ width;
83 | float scaleHeight = newHeight*1.0f / height;
84 | Matrix matrix = new Matrix();
85 | matrix.postScale(scaleWidth, scaleHeight);
86 | thumbImage_left= Bitmap.createBitmap(thumbImage_left, 0, 0, width, height, matrix, true);
87 |
88 | thumbImage_right=thumbImage_left;
89 | thumbPressedImage=thumbImage_left;
90 | // thumbImage_right = BitmapFactory.decodeResource(getResources(), R.drawable.upload_cut_handle_r);
91 | // thumbPressedImage = BitmapFactory.decodeResource(getResources(), R.drawable.upload_cut_handle_pressed);
92 | thumbWidth = newWidth;
93 | thumbHalfWidth = thumbWidth/2;
94 | thumbHalfHeight = newHeight;
95 | rectPaint = getRectPaint();
96 | m_bg = BitmapFactory.decodeResource(getResources(), R.drawable.upload_overlay_black);
97 | m_progress = BitmapFactory.decodeResource(getResources(), R.drawable.upload_overlay_trans);
98 | }
99 |
100 | public void setMin_cut_time(long min_cut_time) {
101 | this.min_cut_time = min_cut_time;
102 | }
103 |
104 | public void setTouchDown(boolean touchDown) {
105 | isTouchDown = touchDown;
106 | }
107 |
108 | public void setVideoPath(String videoPath) {
109 | this.videoPath = videoPath;
110 | }
111 |
112 | /**
113 | * 供外部activity调用,控制是都在拖动的时候打印log信息,默认是false不打印
114 | */
115 | public boolean isNotifyWhileDragging() {
116 | return notifyWhileDragging;
117 | }
118 |
119 |
120 | public void setNotifyWhileDragging(boolean flag) {
121 | this.notifyWhileDragging = flag;
122 | }
123 |
124 | public T getAbsoluteMinValue() {
125 | return absoluteMinValue;
126 | }
127 |
128 |
129 | public T getAbsoluteMaxValue() {
130 | return absoluteMaxValue;
131 | }
132 |
133 |
134 | public T getSelectedMinValue() {
135 | return normalizedToValue(normalizedMinValueTime);
136 | }
137 |
138 | public void setSelectedMinValue(T value) {
139 | // in case absoluteMinValue == absoluteMaxValue, avoid division by zero
140 | // when normalizing.
141 | if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
142 | // activity设置的最大值和最小值相等
143 | setNormalizedMinValue(0d);
144 | } else {
145 | setNormalizedMinValue(valueToNormalized(value));
146 | }
147 | }
148 |
149 | public T getSelectedMaxValue() {
150 | return normalizedToValue(normalizedMaxValueTime);
151 | }
152 |
153 |
154 | public void setSelectedMaxValue(T value) {
155 | // in case absoluteMinValue == absoluteMaxValue, avoid division by zero
156 | // when normalizing.
157 | if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
158 | setNormalizedMaxValue(1d);
159 | } else {
160 | setNormalizedMaxValue(valueToNormalized(value));
161 | }
162 | }
163 |
164 |
165 | public void setOnRangeSeekBarChangeListener(OnRangeSeekBarChangeListener listener) {
166 | this.listener = listener;
167 | }
168 |
169 |
170 | private boolean checkVideoFile(String file_path) {
171 | if (TextUtils.isEmpty(file_path)) {
172 | return true;
173 | }
174 | File check_src_file = new File(file_path);
175 | if (!check_src_file.exists()) {
176 | return false;
177 | }
178 | return true;
179 | }
180 |
181 |
182 | /**
183 | * ACTION_MASK在Android中是应用于多点触摸操作,字面上的意思大概是动作掩码的意思吧。
184 | * 在onTouchEvent(MotionEvent event)中,使用switch
185 | * (event.getAction())可以处理ACTION_DOWN和ACTION_UP事件;使用switch
186 | * (event.getAction() & MotionEvent.ACTION_MASK)
187 | * 就可以处理处理多点触摸的ACTION_POINTER_DOWN和ACTION_POINTER_UP事件。
188 | */
189 | @Override
190 | public boolean onTouchEvent(MotionEvent event) {
191 |
192 | if (isTouchDown) {
193 | return super.onTouchEvent(event);
194 | }
195 |
196 | if (event.getPointerCount() > 1) {
197 | return super.onTouchEvent(event);
198 | }
199 |
200 | if (!isEnabled())
201 | return false;
202 |
203 | if (!checkVideoFile(videoPath)) {
204 | if (listener != null) {
205 | listener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue(), FILE_NOT_EXIST_ACTION, isMin, pressedThumb);
206 | }
207 | return super.onTouchEvent(event);
208 | }
209 |
210 | if (absoluteMaxValuePrim <= min_cut_time) {
211 | return super.onTouchEvent(event);
212 | }
213 |
214 | int pointerIndex;// 记录点击点的index
215 |
216 | final int action = event.getAction();
217 | switch (action & MotionEvent.ACTION_MASK) {
218 |
219 | case MotionEvent.ACTION_DOWN:
220 |
221 | // Remember where the motion event started
222 | // event.getPointerCount() -
223 | // 1得到最后一个点击屏幕的点,点击的点id从0到event.getPointerCount() - 1
224 | mActivePointerId = event.getPointerId(event.getPointerCount() - 1);
225 | pointerIndex = event.findPointerIndex(mActivePointerId);
226 | mDownMotionX = event.getX(pointerIndex);// 得到pointerIndex点击点的X坐标
227 | pressedThumb = evalPressedThumb(mDownMotionX);// 判断touch到的是最大值thumb还是最小值thumb
228 |
229 | // Only handle thumb presses.
230 | if (pressedThumb == null)
231 | return super.onTouchEvent(event);
232 |
233 | setPressed(true);// 设置该控件被按下了
234 | // invalidate();// 通知执行onDraw方法
235 | onStartTrackingTouch();// 置mIsDragging为true,开始追踪touch事件
236 |
237 | trackTouchEvent(event);
238 | attemptClaimDrag();
239 | if (listener != null) {
240 | listener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue(), MotionEvent.ACTION_DOWN, isMin, pressedThumb);
241 | }
242 | break;
243 | case MotionEvent.ACTION_MOVE:
244 | if (pressedThumb != null) {
245 |
246 | if (mIsDragging) {
247 | trackTouchEvent(event);
248 | } else {
249 | // Scroll to follow the motion event
250 | pointerIndex = event.findPointerIndex(mActivePointerId);
251 | final float x = event.getX(pointerIndex);// 手指在控件上点的X坐标
252 | // 手指没有点在最大最小值上,并且在控件上有滑动事件
253 | if (Math.abs(x - mDownMotionX) > mScaledTouchSlop) {
254 | setPressed(true);
255 | Log.e(TAG, "没有拖住最大最小值");// 一直不会执行?
256 | invalidate();
257 | onStartTrackingTouch();
258 | trackTouchEvent(event);
259 | attemptClaimDrag();
260 | }
261 | }
262 |
263 | if (notifyWhileDragging && listener != null) {
264 | listener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue(), MotionEvent.ACTION_MOVE, isMin, pressedThumb);
265 | }
266 | }
267 | break;
268 | case MotionEvent.ACTION_UP:
269 | if (mIsDragging) {
270 | trackTouchEvent(event);
271 | onStopTrackingTouch();
272 | setPressed(false);
273 | } else {
274 | onStartTrackingTouch();
275 | trackTouchEvent(event);
276 | onStopTrackingTouch();
277 | }
278 |
279 | invalidate();
280 | if (listener != null) {
281 | listener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue(), MotionEvent.ACTION_UP, isMin, pressedThumb);
282 | }
283 | pressedThumb = null;// 手指抬起,则置被touch到的thumb为空
284 | break;
285 | case MotionEvent.ACTION_POINTER_DOWN: {
286 | final int index = event.getPointerCount() - 1;
287 | // final int index = ev.getActionIndex();
288 | mDownMotionX = event.getX(index);
289 | mActivePointerId = event.getPointerId(index);
290 | invalidate();
291 | break;
292 | }
293 | case MotionEvent.ACTION_POINTER_UP:
294 | onSecondaryPointerUp(event);
295 | invalidate();
296 | break;
297 | case MotionEvent.ACTION_CANCEL:
298 | if (mIsDragging) {
299 | onStopTrackingTouch();
300 | setPressed(false);
301 | }
302 | invalidate(); // see above explanation
303 | break;
304 | }
305 | return true;
306 | }
307 |
308 |
309 | private final void onSecondaryPointerUp(MotionEvent ev) {
310 | final int pointerIndex = (ev.getAction() & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;
311 |
312 | final int pointerId = ev.getPointerId(pointerIndex);
313 | if (pointerId == mActivePointerId) {
314 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
315 | mDownMotionX = ev.getX(newPointerIndex);
316 | mActivePointerId = ev.getPointerId(newPointerIndex);
317 | }
318 | }
319 |
320 | /**
321 | * 一直追踪touch事件,刷新view
322 | *
323 | * @param event
324 | */
325 | private final void trackTouchEvent(MotionEvent event) {
326 | if (event.getPointerCount() > 1) return;
327 | Log.e(TAG, "trackTouchEvent: " + event.getAction() + " x: " + event.getX());
328 | final int pointerIndex = event.findPointerIndex(mActivePointerId);// 得到按下点的index
329 | float x = 0;
330 | try {
331 | x = event.getX(pointerIndex);//
332 | } catch (Exception e) {
333 | return;
334 | }
335 |
336 | if (Thumb.MIN.equals(pressedThumb)) {
337 | // screenToNormalized(x)-->得到规格化的0-1的值
338 | setNormalizedMinValue(screenToNormalized(x, 0));
339 | } else if (Thumb.MAX.equals(pressedThumb)) {
340 | setNormalizedMaxValue(screenToNormalized(x, 1));
341 | }
342 | }
343 |
344 | /**
345 | * 试图告诉父view不要拦截子控件的drag
346 | */
347 | private void attemptClaimDrag() {
348 | if (getParent() != null) {
349 | getParent().requestDisallowInterceptTouchEvent(true);
350 | }
351 | }
352 |
353 | void onStartTrackingTouch() {
354 | mIsDragging = true;
355 | }
356 |
357 |
358 | void onStopTrackingTouch() {
359 | mIsDragging = false;
360 | }
361 |
362 |
363 | @Override
364 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
365 | int width = 250;
366 | if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) {
367 | width = MeasureSpec.getSize(widthMeasureSpec);
368 | }
369 | int height = 100;
370 | if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) {
371 | height = MeasureSpec.getSize(heightMeasureSpec);
372 | }
373 | setMeasuredDimension(width, height);
374 | }
375 |
376 |
377 | @Override
378 | protected synchronized void onDraw(Canvas canvas) {
379 | super.onDraw(canvas);
380 | float bg_middle_left = 0;// 需要平铺的中间背景的开始坐标
381 | float bg_middle_right = getWidth();// 需要平铺的中间背景的开始坐标
382 |
383 | float scale = (bg_middle_right - bg_middle_left) / m_progress.getWidth();// 上层最大最小值间距离与m_progress比例
384 | float rangeL = normalizedToScreen(normalizedMinValue);
385 | float rangeR = normalizedToScreen(normalizedMaxValue);
386 | float pro_scale = (rangeR - rangeL) / m_progress.getWidth();// 上层最大最小值间距离与m_progress比例
387 | if (pro_scale > 0) {
388 | try {
389 | Matrix mx = new Matrix();
390 | mx.postScale(scale, 1f);
391 | Bitmap m_bg_new = Bitmap.createBitmap(m_bg, 0, 0, m_bg.getWidth(), m_progress.getHeight(), mx, true);
392 |
393 | Matrix pro_mx = new Matrix();
394 | pro_mx.postScale(pro_scale, 1f);
395 | Bitmap m_progress_new = Bitmap.createBitmap(m_progress, 0, 0, m_progress.getWidth(),
396 | m_progress.getHeight(), pro_mx, true);
397 |
398 | canvas.drawBitmap(m_progress_new, rangeL, thumbPaddingTop, paint);
399 | Bitmap m_bg_new1 = Bitmap.createBitmap(m_bg_new, 0, 0, (int) (rangeL - bg_middle_left) + (int) thumbWidth / 2, m_progress.getHeight());
400 | canvas.drawBitmap(m_bg_new1, bg_middle_left, thumbPaddingTop, paint);
401 |
402 | Bitmap m_bg_new2 = Bitmap.createBitmap(m_bg_new, (int) (rangeR - thumbWidth / 2), 0, (int) (getWidth() - rangeR) + (int) thumbWidth / 2, m_progress.getHeight());
403 | canvas.drawBitmap(m_bg_new2, (int) (rangeR - thumbWidth / 2), thumbPaddingTop, paint);
404 |
405 | canvas.drawRect(rangeL, thumbPaddingTop, rangeR, thumbPaddingTop + UIUtil.dip2px(getContext(),2), rectPaint);
406 | canvas.drawRect(rangeL, getHeight() - UIUtil.dip2px(getContext(),2), rangeR, getHeight(), rectPaint);
407 | } catch (Exception e) {
408 | // 当pro_scale非常小,例如width=12,Height=48,pro_scale=0.01979065时,
409 | // 宽高按比例计算后值为0.237、0.949,系统强转为int型后宽就变成0了。就出现非法参数异常
410 | Log.e(TAG,
411 | "IllegalArgumentException--width=" + m_progress.getWidth() + "Height=" + m_progress.getHeight()
412 | + "pro_scale=" + pro_scale, e);
413 | }
414 |
415 | }
416 | // draw minimum thumb
417 | drawThumb(normalizedToScreen(normalizedMinValue), false, canvas, true);
418 | // draw maximum thumb
419 | float drawRight = normalizedToScreen(normalizedMaxValue);
420 | // if (drawRight>(getWidth()-thumbWidth)) {
421 | // drawRight = getWidth()-thumbWidth;
422 | // }
423 | drawThumb(drawRight, false, canvas, false);
424 | }
425 |
426 | @Override
427 | protected Parcelable onSaveInstanceState() {
428 | final Bundle bundle = new Bundle();
429 | bundle.putParcelable("SUPER", super.onSaveInstanceState());
430 | bundle.putDouble("MIN", normalizedMinValue);
431 | bundle.putDouble("MAX", normalizedMaxValue);
432 | bundle.putDouble("MIN_TIME", normalizedMinValueTime);
433 | bundle.putDouble("MAX_TIME", normalizedMaxValueTime);
434 | return bundle;
435 | }
436 |
437 | @Override
438 | protected void onRestoreInstanceState(Parcelable parcel) {
439 | final Bundle bundle = (Bundle) parcel;
440 | super.onRestoreInstanceState(bundle.getParcelable("SUPER"));
441 | normalizedMinValue = bundle.getDouble("MIN");
442 | normalizedMaxValue = bundle.getDouble("MAX");
443 | normalizedMinValueTime = bundle.getDouble("MIN_TIME");
444 | normalizedMaxValueTime = bundle.getDouble("MAX_TIME");
445 | }
446 |
447 | private void drawThumb(float screenCoord, boolean pressed, Canvas canvas, boolean isLeft) {
448 | canvas.drawBitmap(pressed ? thumbPressedImage : (isLeft ? thumbImage_left : thumbImage_right), screenCoord - (isLeft ? 0 : thumbWidth), (pressed ? thumbPressPaddingTop : thumbPaddingTop), paint);
449 | }
450 |
451 | private Paint getRectPaint() {
452 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
453 | p.setStyle(Paint.Style.FILL);
454 | p.setColor(Color.parseColor("#ffffff"));
455 | return p;
456 | }
457 |
458 | /**
459 | * 重新运算求出参数的内容
460 | *
461 | * @param touchX touchX
462 | * @return 被touch的是空还是最大值或最小值
463 | */
464 | private Thumb evalPressedThumb(float touchX) {
465 | Thumb result = null;
466 | boolean minThumbPressed = isInThumbRange(touchX, normalizedMinValue, 2);// 触摸点是否在最小值图片范围内
467 | boolean maxThumbPressed = isInThumbRange(touchX, normalizedMaxValue, 2);
468 | if (minThumbPressed && maxThumbPressed) {
469 | // 如果两个thumbs重叠在一起,无法判断拖动哪个,做以下处理
470 | // 触摸点在屏幕右侧,则判断为touch到了最小值thumb,反之判断为touch到了最大值thumb
471 | result = (touchX / getWidth() > 0.5f) ? Thumb.MIN : Thumb.MAX;
472 | } else if (minThumbPressed) {
473 | result = Thumb.MIN;
474 | } else if (maxThumbPressed) {
475 | result = Thumb.MAX;
476 | }
477 | return result;
478 | }
479 |
480 | private boolean isInThumbRange(float touchX, double normalizedThumbValue, double scale) {
481 | // 当前触摸点X坐标-最小值图片中心点在屏幕的X坐标之差<=最小点图片的宽度的一般
482 | // 即判断触摸点是否在以最小值图片中心为原点,宽度一半为半径的圆内。
483 | return Math.abs(touchX - normalizedToScreen(normalizedThumbValue)) <= thumbHalfWidth * scale;
484 | }
485 |
486 |
487 | private boolean isInThumbRangeLeft(float touchX, double normalizedThumbValue, double scale) {
488 | // 当前触摸点X坐标-最小值图片中心点在屏幕的X坐标之差<=最小点图片的宽度的一般
489 | // 即判断触摸点是否在以最小值图片中心为原点,宽度一半为半径的圆内。
490 | return Math.abs(touchX - normalizedToScreen(normalizedThumbValue) - thumbWidth) <= thumbHalfWidth * scale;
491 | }
492 |
493 |
494 | public void setNormalizedMinValue(double value) {
495 | normalizedMinValue = Math.max(0d, Math.min(1d, Math.min(value, normalizedMaxValue)));
496 | invalidate();// 重新绘制此view
497 | }
498 |
499 |
500 | public void setNormalizedMaxValue(double value) {
501 | normalizedMaxValue = Math.max(0d, Math.min(1d, Math.max(value, normalizedMinValue)));
502 | invalidate();// 重新绘制此view
503 | }
504 |
505 |
506 | @SuppressWarnings("unchecked")
507 | private T normalizedToValue(double normalized) {
508 | return (T) numberType.toNumber(absoluteMinValuePrim + normalized
509 | * (absoluteMaxValuePrim - absoluteMinValuePrim));
510 | }
511 |
512 | private double valueToNormalized(T value) {
513 | if (0 == absoluteMaxValuePrim - absoluteMinValuePrim) {
514 | // prevent division by zero, simply return 0.
515 | return 0d;
516 | }
517 | return (value.doubleValue() - absoluteMinValuePrim) / (absoluteMaxValuePrim - absoluteMinValuePrim);
518 | }
519 |
520 |
521 | private float normalizedToScreen(double normalizedCoord) {
522 | // getWidth() - 2 * padding --> 整个View宽度减去左右padding,
523 | // 即减去一个thumb的宽度,即两个thumb可滑动的范围长度
524 |
525 | // normalizedCoord * (getWidth() - 2 * padding)
526 | // 规格化值与长度的成绩,即该点在屏幕上的相对x坐标值
527 |
528 | // padding + normalizedCoord * (getWidth() - 2 * padding)
529 | // 该点在屏幕上的绝对x坐标值
530 |
531 | return (float) (padding + normalizedCoord * (getWidth() - 2 * padding));
532 | // return (float) (normalizedCoord * getWidth());
533 | }
534 |
535 |
536 | private double screenToNormalized(float screenCoord, int position) {
537 | int width = getWidth();
538 | if (width <= 2 * padding) {
539 | // prevent division by zero, simply return 0.
540 | return 0d;
541 | } else {
542 | isMin = false;
543 | double current_width = screenCoord;
544 | float rangeL = normalizedToScreen(normalizedMinValue);
545 | float rangeR = normalizedToScreen(normalizedMaxValue);
546 |
547 |
548 | double min = min_cut_time / (absoluteMaxValuePrim - absoluteMinValuePrim) * (width - thumbWidth * 2);
549 |
550 | // if (absoluteMaxValuePrim>15*60*1000) {
551 | //
552 | // } else {
553 | // min_width = Math.round(min+0.5d);
554 | // }
555 | // min_width = min;
556 | if (absoluteMaxValuePrim > 5 * 60 * 1000) {//大于5分钟的精确小数四位
557 | DecimalFormat df = new DecimalFormat("0.0000");
558 | min_width = Double.parseDouble(df.format(min));
559 | } else {
560 | min_width = Math.round(min + 0.5d);
561 | }
562 | if (position == 0) {
563 | if (isInThumbRangeLeft(screenCoord, normalizedMinValue, 0.5)) {
564 | return normalizedMinValue;
565 | }
566 |
567 | float rightPosition = (getWidth() - rangeR) >= 0 ? (getWidth() - rangeR) : 0;
568 | double left_length = getValueLength() - (rightPosition + min_width);
569 |
570 |
571 | if (current_width > rangeL) {
572 | current_width = rangeL + (current_width - rangeL);
573 | } else if (current_width <= rangeL) {
574 | current_width = rangeL - (rangeL - current_width);
575 | }
576 |
577 | if (current_width > left_length) {
578 | isMin = true;
579 | current_width = left_length;
580 | }
581 |
582 | if (current_width < thumbWidth * 2 / 3) {
583 | current_width = 0;
584 | }
585 |
586 | double resultTime = (current_width - padding) / (width - 2 * thumbWidth);
587 | normalizedMinValueTime = Math.min(1d, Math.max(0d, resultTime));
588 | double result = (current_width - padding) / (width - 2 * padding);
589 | return Math.min(1d, Math.max(0d, result));// 保证该该值为0-1之间,但是什么时候这个判断有用呢?
590 | } else {
591 | if (isInThumbRange(screenCoord, normalizedMaxValue, 0.5)) {
592 | return normalizedMaxValue;
593 | }
594 |
595 | double right_length = getValueLength() - (rangeL + min_width);
596 | if (current_width > rangeR) {
597 | current_width = rangeR + (current_width - rangeR);
598 | } else if (current_width <= rangeR) {
599 | current_width = rangeR - (rangeR - current_width);
600 | }
601 |
602 | double paddingRight = getWidth() - current_width;
603 |
604 | if (paddingRight > right_length) {
605 | isMin = true;
606 | current_width = getWidth() - right_length;
607 | paddingRight = right_length;
608 | }
609 |
610 | if (paddingRight < thumbWidth * 2 / 3) {
611 | current_width = getWidth();
612 | paddingRight = 0;
613 | }
614 |
615 | double resultTime = (paddingRight - padding) / (width - 2 * thumbWidth);
616 | resultTime = 1 - resultTime;
617 | normalizedMaxValueTime = Math.min(1d, Math.max(0d, resultTime));
618 | double result = (current_width - padding) / (width - 2 * padding);
619 | return Math.min(1d, Math.max(0d, result));// 保证该该值为0-1之间,但是什么时候这个判断有用呢?
620 | }
621 |
622 | }
623 | }
624 |
625 |
626 | private int getValueLength() {
627 | return (getWidth() - 2 * thumbWidth);
628 | }
629 |
630 |
631 | public interface OnRangeSeekBarChangeListener {
632 | void onRangeSeekBarValuesChanged(RangeSeekBar2> bar, T minValue, T maxValue, int action, boolean isMin, Thumb pressedThumb);
633 | }
634 |
635 | /**
636 | * 只有两个值,一个代表滑动条上的最大值,一个代表滑动条上的最小值
637 | */
638 | public enum Thumb {
639 | MIN, MAX
640 | }
641 |
642 | private enum NumberType {
643 | LONG, DOUBLE, INTEGER, FLOAT, SHORT, BYTE, BIG_DECIMAL;
644 |
645 | public static NumberType fromNumber(E value) throws IllegalArgumentException {
646 | if (value instanceof Long) {
647 | return LONG;
648 | }
649 | if (value instanceof Double) {
650 | return DOUBLE;
651 | }
652 | if (value instanceof Integer) {
653 | return INTEGER;
654 | }
655 | if (value instanceof Float) {
656 | return FLOAT;
657 | }
658 | if (value instanceof Short) {
659 | return SHORT;
660 | }
661 | if (value instanceof Byte) {
662 | return BYTE;
663 | }
664 | if (value instanceof BigDecimal) {
665 | return BIG_DECIMAL;
666 | }
667 | throw new IllegalArgumentException("Number class '" + value.getClass().getName() + "' is not supported");
668 | }
669 |
670 | public Number toNumber(double value) {
671 | // this代表调用该方法的对象,即枚举类中的枚举类型之一
672 | switch (this) {
673 | case LONG:
674 | return new Long((long) value);
675 | case DOUBLE:
676 | return value;
677 | case INTEGER:
678 | return new Integer((int) value);
679 | case FLOAT:
680 | return new Float(value);
681 | case SHORT:
682 | return new Short((short) value);
683 | case BYTE:
684 | return new Byte((byte) value);
685 | case BIG_DECIMAL:
686 | return new BigDecimal(value);
687 | }
688 | throw new InstantiationError("can't convert " + this + " to a Number object");
689 | }
690 | }
691 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/gxz/example/videoedit/UIUtil.java:
--------------------------------------------------------------------------------
1 | package com.gxz.example.videoedit;
2 |
3 | import android.content.Context;
4 | import android.util.DisplayMetrics;
5 | import android.view.WindowManager;
6 |
7 | import static android.content.Context.WINDOW_SERVICE;
8 |
9 | /**
10 | * ================================================
11 | * 作 者:顾修忠-guxiuzhong@youku.com/gfj19900401@163.com
12 | * 版 本:
13 | * 创建日期:2017/4/8-下午3:48
14 | * 描 述:
15 | * 修订历史:
16 | * ================================================
17 | */
18 |
19 | public class UIUtil {
20 | public static int dip2px(Context context, int dip) {
21 | float scale = context.getResources().getDisplayMetrics().density;
22 | return (int) ((float) dip * scale + 0.5F);
23 | }
24 |
25 | public static int getScreenWidth(Context context) {
26 | DisplayMetrics metric = new DisplayMetrics();
27 | WindowManager wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
28 | wm.getDefaultDisplay().getMetrics(metric);
29 | return metric.widthPixels;
30 | }
31 |
32 | public static int getScreenHeight(Context context) {
33 | DisplayMetrics metric = new DisplayMetrics();
34 | WindowManager wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
35 | wm.getDefaultDisplay().getMetrics(metric);
36 | return metric.heightPixels;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/gxz/example/videoedit/VideoEditActivity.java:
--------------------------------------------------------------------------------
1 | package com.gxz.example.videoedit;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.media.MediaPlayer;
5 | import android.os.Bundle;
6 | import android.os.Environment;
7 | import android.os.Handler;
8 | import android.os.Message;
9 | import android.support.v7.app.AppCompatActivity;
10 | import android.support.v7.widget.LinearLayoutManager;
11 | import android.support.v7.widget.RecyclerView;
12 | import android.text.TextUtils;
13 | import android.util.Log;
14 | import android.view.MotionEvent;
15 | import android.view.View;
16 | import android.view.ViewConfiguration;
17 | import android.view.animation.LinearInterpolator;
18 | import android.widget.FrameLayout;
19 | import android.widget.ImageView;
20 | import android.widget.LinearLayout;
21 | import android.widget.Toast;
22 | import android.widget.VideoView;
23 |
24 | import java.io.File;
25 | import java.lang.ref.WeakReference;
26 |
27 | /**
28 | * ================================================
29 | * 作 者:顾修忠-guxiuzhong@youku.com/gfj19900401@163.com
30 | * 版 本:
31 | * 创建日期:2017/4/8-下午3:48
32 | * 描 述:
33 | * 修订历史:
34 | * ================================================
35 | */
36 | public class VideoEditActivity extends AppCompatActivity {
37 | private static final String TAG = VideoEditActivity.class.getSimpleName();
38 | private static final long MIN_CUT_DURATION = 3 * 1000L;// 最小剪辑时间3s
39 | private static final long MAX_CUT_DURATION = 60 * 1000L;//视频最多剪切多长时间
40 | private static final int MAX_COUNT_RANGE = 10;//seekBar的区域内一共有多少张图片
41 | private LinearLayout seekBarLayout;
42 | private ExtractVideoInfoUtil mExtractVideoInfoUtil;
43 | private int mMaxWidth;
44 |
45 | private long duration;
46 | private RangeSeekBar seekBar;
47 | private VideoView mVideoView;
48 | private RecyclerView mRecyclerView;
49 | private ImageView positionIcon;
50 | private VideoEditAdapter videoEditAdapter;
51 | private float averageMsPx;//每毫秒所占的px
52 | private float averagePxMs;//每px所占用的ms毫秒
53 | private String OutPutFileDirPath;
54 | private ExtractFrameWorkThread mExtractFrameWorkThread;
55 | private String path;
56 | private long leftProgress, rightProgress;
57 | private long scrollPos = 0;
58 | private int mScaledTouchSlop;
59 | private int lastScrollX;
60 | private boolean isSeeking;
61 |
62 | @Override
63 | protected void onCreate(Bundle savedInstanceState) {
64 | super.onCreate(savedInstanceState);
65 | setContentView(R.layout.activity_video_edit);
66 | initData();
67 | initView();
68 | initEditVideo();
69 | initPlay();
70 | }
71 |
72 | private void initData() {
73 | path = Environment.getExternalStorageDirectory() + "/2.mp4";
74 | //for video check
75 | if (!new File(path).exists()) {
76 | Toast.makeText(this, "视频文件不存在", Toast.LENGTH_LONG).show();
77 | finish();
78 | }
79 | mExtractVideoInfoUtil = new ExtractVideoInfoUtil(path);
80 | duration = Long.valueOf(mExtractVideoInfoUtil.getVideoLength());
81 |
82 |
83 | mMaxWidth = UIUtil.getScreenWidth(this) - UIUtil.dip2px(this, 70);
84 | mScaledTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
85 |
86 | }
87 |
88 | private void initView() {
89 | seekBarLayout = (LinearLayout) findViewById(R.id.id_seekBarLayout);
90 | mVideoView = (VideoView) findViewById(R.id.uVideoView);
91 | positionIcon = (ImageView) findViewById(R.id.positionIcon);
92 | mRecyclerView = (RecyclerView) findViewById(R.id.id_rv_id);
93 | mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
94 | videoEditAdapter = new VideoEditAdapter(this,
95 | (UIUtil.getScreenWidth(this) - UIUtil.dip2px(this, 70)) / 10);
96 | mRecyclerView.setAdapter(videoEditAdapter);
97 | mRecyclerView.addOnScrollListener(mOnScrollListener);
98 | }
99 |
100 |
101 | private void initEditVideo() {
102 | //for video edit
103 | long startPosition = 0;
104 | long endPosition = duration;
105 | int thumbnailsCount;
106 | int rangeWidth;
107 | boolean isOver_60_s;
108 | if (endPosition <= MAX_CUT_DURATION) {
109 | isOver_60_s = false;
110 | thumbnailsCount = MAX_COUNT_RANGE;
111 | rangeWidth = mMaxWidth;
112 | } else {
113 | isOver_60_s = true;
114 | thumbnailsCount = (int) (endPosition * 1.0f / (MAX_CUT_DURATION * 1.0f) * MAX_COUNT_RANGE);
115 | rangeWidth = mMaxWidth / MAX_COUNT_RANGE * thumbnailsCount;
116 | }
117 | mRecyclerView.addItemDecoration(new EditSpacingItemDecoration(UIUtil.dip2px(this, 35), thumbnailsCount));
118 |
119 | //init seekBar
120 | if (isOver_60_s) {
121 | seekBar = new RangeSeekBar(this, 0L, MAX_CUT_DURATION);
122 | seekBar.setSelectedMinValue(0L);
123 | seekBar.setSelectedMaxValue(MAX_CUT_DURATION);
124 | } else {
125 | seekBar = new RangeSeekBar(this, 0L, endPosition);
126 | seekBar.setSelectedMinValue(0L);
127 | seekBar.setSelectedMaxValue(endPosition);
128 | }
129 | seekBar.setMin_cut_time(MIN_CUT_DURATION);//设置最小裁剪时间
130 | seekBar.setNotifyWhileDragging(true);
131 | seekBar.setOnRangeSeekBarChangeListener(mOnRangeSeekBarChangeListener);
132 | seekBarLayout.addView(seekBar);
133 |
134 | Log.d(TAG, "-------thumbnailsCount--->>>>" + thumbnailsCount);
135 | averageMsPx = duration * 1.0f / rangeWidth * 1.0f;
136 | Log.d(TAG, "-------rangeWidth--->>>>" + rangeWidth);
137 | Log.d(TAG, "-------localMedia.getDuration()--->>>>" + duration);
138 | Log.d(TAG, "-------averageMsPx--->>>>" + averageMsPx);
139 | OutPutFileDirPath = PictureUtils.getSaveEditThumbnailDir(this);
140 | int extractW = (UIUtil.getScreenWidth(this) - UIUtil.dip2px(this, 70)) / MAX_COUNT_RANGE;
141 | int extractH = UIUtil.dip2px(this, 55);
142 | mExtractFrameWorkThread = new ExtractFrameWorkThread(extractW, extractH, mUIHandler, path, OutPutFileDirPath, startPosition, endPosition, thumbnailsCount);
143 | mExtractFrameWorkThread.start();
144 |
145 | //init pos icon start
146 | leftProgress = 0;
147 | if (isOver_60_s) {
148 | rightProgress = MAX_CUT_DURATION;
149 | } else {
150 | rightProgress = endPosition;
151 | }
152 | averagePxMs = (mMaxWidth * 1.0f / (rightProgress - leftProgress));
153 | Log.d(TAG, "------averagePxMs----:>>>>>" + averagePxMs);
154 |
155 |
156 | }
157 |
158 |
159 | private void initPlay() {
160 | mVideoView.setVideoPath(path);
161 | //设置videoview的OnPrepared监听
162 | mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
163 | @Override
164 | public void onPrepared(MediaPlayer mp) {
165 | //设置MediaPlayer的OnSeekComplete监听
166 | mp.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
167 | @Override
168 | public void onSeekComplete(MediaPlayer mp) {
169 | Log.d(TAG, "------ok----real---start-----");
170 | Log.d(TAG, "------isSeeking-----"+isSeeking);
171 | if (!isSeeking) {
172 | videoStart();
173 | }
174 | }
175 | });
176 | }
177 | });
178 | //first
179 | videoStart();
180 | }
181 |
182 | private boolean isOverScaledTouchSlop;
183 |
184 | private final RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
185 | @Override
186 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
187 | super.onScrollStateChanged(recyclerView, newState);
188 | Log.d(TAG, "-------newState:>>>>>" + newState);
189 | if (newState == RecyclerView.SCROLL_STATE_IDLE) {
190 | isSeeking = false;
191 | // videoStart();
192 | } else {
193 | isSeeking = true;
194 | if (isOverScaledTouchSlop && mVideoView != null && mVideoView.isPlaying()) {
195 | videoPause();
196 | }
197 | }
198 | }
199 |
200 | @Override
201 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
202 | super.onScrolled(recyclerView, dx, dy);
203 | isSeeking = false;
204 | int scrollX = getScrollXDistance();
205 | //达不到滑动的距离
206 | if (Math.abs(lastScrollX - scrollX) < mScaledTouchSlop) {
207 | isOverScaledTouchSlop = false;
208 | return;
209 | }
210 | isOverScaledTouchSlop = true;
211 | Log.d(TAG, "-------scrollX:>>>>>" + scrollX);
212 | //初始状态,why ? 因为默认的时候有35dp的空白!
213 | if (scrollX == -UIUtil.dip2px(VideoEditActivity.this, 35)) {
214 | scrollPos = 0;
215 | } else {
216 | // why 在这里处理一下,因为onScrollStateChanged早于onScrolled回调
217 | if (mVideoView != null && mVideoView.isPlaying()) {
218 | videoPause();
219 | }
220 | isSeeking = true;
221 | scrollPos = (long) (averageMsPx * (UIUtil.dip2px(VideoEditActivity.this, 35) + scrollX));
222 | Log.d(TAG, "-------scrollPos:>>>>>" + scrollPos);
223 | leftProgress = seekBar.getSelectedMinValue() + scrollPos;
224 | rightProgress = seekBar.getSelectedMaxValue() + scrollPos;
225 | Log.d(TAG, "-------leftProgress:>>>>>" + leftProgress);
226 | mVideoView.seekTo((int) leftProgress);
227 | }
228 | lastScrollX = scrollX;
229 | }
230 | };
231 |
232 | /**
233 | * 水平滑动了多少px
234 | *
235 | * @return int px
236 | */
237 | private int getScrollXDistance() {
238 | LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
239 | int position = layoutManager.findFirstVisibleItemPosition();
240 | View firstVisibleChildView = layoutManager.findViewByPosition(position);
241 | int itemWidth = firstVisibleChildView.getWidth();
242 | return (position) * itemWidth - firstVisibleChildView.getLeft();
243 | }
244 |
245 | private ValueAnimator animator;
246 |
247 | private void anim() {
248 | Log.d(TAG, "--anim--onProgressUpdate---->>>>>>>" + mVideoView.getCurrentPosition());
249 | if (positionIcon.getVisibility() == View.GONE) {
250 | positionIcon.setVisibility(View.VISIBLE);
251 | }
252 | final FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) positionIcon.getLayoutParams();
253 | int start = (int) (UIUtil.dip2px(this, 35) + (leftProgress/*mVideoView.getCurrentPosition()*/ - scrollPos) * averagePxMs);
254 | int end = (int) (UIUtil.dip2px(this, 35) + (rightProgress - scrollPos) * averagePxMs);
255 | animator = ValueAnimator
256 | .ofInt(start, end)
257 | .setDuration((rightProgress - scrollPos) - (leftProgress/*mVideoView.getCurrentPosition()*/ - scrollPos));
258 | animator.setInterpolator(new LinearInterpolator());
259 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
260 | @Override
261 | public void onAnimationUpdate(ValueAnimator animation) {
262 | params.leftMargin = (int) animation.getAnimatedValue();
263 | positionIcon.setLayoutParams(params);
264 | }
265 | });
266 | animator.start();
267 | }
268 |
269 | private final MainHandler mUIHandler = new MainHandler(this);
270 |
271 | private static class MainHandler extends Handler {
272 | private final WeakReference mActivity;
273 |
274 | MainHandler(VideoEditActivity activity) {
275 | mActivity = new WeakReference<>(activity);
276 | }
277 |
278 | @Override
279 | public void handleMessage(Message msg) {
280 | VideoEditActivity activity = mActivity.get();
281 | if (activity != null) {
282 | if (msg.what == ExtractFrameWorkThread.MSG_SAVE_SUCCESS) {
283 | if (activity.videoEditAdapter != null) {
284 | VideoEditInfo info = (VideoEditInfo) msg.obj;
285 | activity.videoEditAdapter.addItemVideoInfo(info);
286 | }
287 | }
288 | }
289 | }
290 | }
291 |
292 | private final RangeSeekBar.OnRangeSeekBarChangeListener mOnRangeSeekBarChangeListener = new RangeSeekBar.OnRangeSeekBarChangeListener() {
293 | @Override
294 | public void onRangeSeekBarValuesChanged(RangeSeekBar bar, long minValue, long maxValue, int action, boolean isMin, RangeSeekBar.Thumb pressedThumb) {
295 | Log.d(TAG, "-----minValue----->>>>>>" + minValue);
296 | Log.d(TAG, "-----maxValue----->>>>>>" + maxValue);
297 | leftProgress = minValue + scrollPos;
298 | rightProgress = maxValue + scrollPos;
299 | Log.d(TAG, "-----leftProgress----->>>>>>" + leftProgress);
300 | Log.d(TAG, "-----rightProgress----->>>>>>" + rightProgress);
301 | switch (action) {
302 | case MotionEvent.ACTION_DOWN:
303 | Log.d(TAG, "-----ACTION_DOWN---->>>>>>");
304 | isSeeking = false;
305 | videoPause();
306 | break;
307 | case MotionEvent.ACTION_MOVE:
308 | Log.d(TAG, "-----ACTION_MOVE---->>>>>>");
309 | isSeeking = true;
310 | mVideoView.seekTo((int) (pressedThumb == RangeSeekBar.Thumb.MIN ?
311 | leftProgress : rightProgress));
312 | break;
313 | case MotionEvent.ACTION_UP:
314 | Log.d(TAG, "-----ACTION_UP--leftProgress--->>>>>>" + leftProgress);
315 | isSeeking = false;
316 | //从minValue开始播
317 | mVideoView.seekTo((int) leftProgress);
318 | // videoStart();
319 | break;
320 | default:
321 | break;
322 | }
323 | }
324 | };
325 |
326 |
327 | private void videoStart() {
328 | Log.d(TAG, "----videoStart----->>>>>>>");
329 | mVideoView.start();
330 | positionIcon.clearAnimation();
331 | if (animator != null && animator.isRunning()) {
332 | animator.cancel();
333 | }
334 | anim();
335 | handler.removeCallbacks(run);
336 | handler.post(run);
337 | }
338 |
339 | private void videoProgressUpdate() {
340 | long currentPosition = mVideoView.getCurrentPosition();
341 | Log.d(TAG, "----onProgressUpdate-cp---->>>>>>>" + currentPosition);
342 | if (currentPosition >= (rightProgress)) {
343 | mVideoView.seekTo((int) leftProgress);
344 | positionIcon.clearAnimation();
345 | if (animator != null && animator.isRunning()) {
346 | animator.cancel();
347 | }
348 | anim();
349 | }
350 | }
351 |
352 | private void videoPause() {
353 | isSeeking = false;
354 | if (mVideoView != null && mVideoView.isPlaying()) {
355 | mVideoView.pause();
356 | handler.removeCallbacks(run);
357 | }
358 | Log.d(TAG, "----videoPause----->>>>>>>");
359 | if (positionIcon.getVisibility() == View.VISIBLE) {
360 | positionIcon.setVisibility(View.GONE);
361 | }
362 | positionIcon.clearAnimation();
363 | if (animator != null && animator.isRunning()) {
364 | animator.cancel();
365 | }
366 | }
367 |
368 |
369 | @Override
370 | protected void onResume() {
371 | super.onResume();
372 | if (mVideoView != null) {
373 | mVideoView.seekTo((int) leftProgress);
374 | // videoStart();
375 | }
376 | }
377 |
378 | @Override
379 | protected void onPause() {
380 | super.onPause();
381 | if (mVideoView != null && mVideoView.isPlaying()) {
382 | videoPause();
383 | }
384 | }
385 |
386 | private Handler handler = new Handler();
387 | private Runnable run = new Runnable() {
388 |
389 | @Override
390 | public void run() {
391 | videoProgressUpdate();
392 | handler.postDelayed(run, 1000);
393 | }
394 | };
395 |
396 | @Override
397 | protected void onDestroy() {
398 | super.onDestroy();
399 | if (animator != null) {
400 | animator.cancel();
401 | }
402 | if (mVideoView != null) {
403 | mVideoView.stopPlayback();
404 | }
405 | if (mExtractVideoInfoUtil != null) {
406 | mExtractVideoInfoUtil.release();
407 | }
408 | mRecyclerView.removeOnScrollListener(mOnScrollListener);
409 | if (mExtractFrameWorkThread != null) {
410 | mExtractFrameWorkThread.stopExtract();
411 | }
412 | mUIHandler.removeCallbacksAndMessages(null);
413 | handler.removeCallbacksAndMessages(null);
414 | if (!TextUtils.isEmpty(OutPutFileDirPath)) {
415 | PictureUtils.deleteFile(new File(OutPutFileDirPath));
416 | }
417 | }
418 | }
419 |
--------------------------------------------------------------------------------
/app/src/main/java/com/gxz/example/videoedit/VideoEditAdapter.java:
--------------------------------------------------------------------------------
1 | package com.gxz.example.videoedit;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.ImageView;
9 | import android.widget.LinearLayout;
10 |
11 | import com.bumptech.glide.Glide;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | /**
17 | * ================================================
18 | * 作 者:顾修忠-guxiuzhong@youku.com/gfj19900401@163.com
19 | * 版 本:
20 | * 创建日期:2017/3/2-下午7:46
21 | * 描 述:
22 | * 修订历史:
23 | * ================================================
24 | */
25 |
26 | public class VideoEditAdapter extends RecyclerView.Adapter {
27 | private List lists = new ArrayList<>();
28 | private LayoutInflater inflater;
29 |
30 | private int itemW;
31 | private Context context;
32 |
33 | public VideoEditAdapter(Context context, int itemW) {
34 | this.context = context;
35 | this.inflater = LayoutInflater.from(context);
36 | this.itemW = itemW;
37 | }
38 |
39 | @Override
40 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
41 | return new EditViewHolder(inflater.inflate(R.layout.video_item, parent, false));
42 | }
43 |
44 | @Override
45 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
46 | EditViewHolder viewHolder = (EditViewHolder) holder;
47 | Glide.with(context)
48 | .load("file://" + lists.get(position).path)
49 | .into(viewHolder.img);
50 | }
51 |
52 | @Override
53 | public int getItemCount() {
54 | return lists.size();
55 | }
56 |
57 | private final class EditViewHolder extends RecyclerView.ViewHolder {
58 | public ImageView img;
59 |
60 | EditViewHolder(View itemView) {
61 | super(itemView);
62 | img = (ImageView) itemView.findViewById(R.id.id_image);
63 | LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) img.getLayoutParams();
64 | layoutParams.width = itemW;
65 | img.setLayoutParams(layoutParams);
66 | }
67 | }
68 |
69 | public void addItemVideoInfo(VideoEditInfo info) {
70 | lists.add(info);
71 | notifyItemInserted(lists.size());
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/com/gxz/example/videoedit/VideoEditInfo.java:
--------------------------------------------------------------------------------
1 | package com.gxz.example.videoedit;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * ================================================
7 | * 作 者:顾修忠-guxiuzhong@youku.com/gfj19900401@163.com
8 | * 版 本:
9 | * 创建日期:2017/3/2-下午8:52
10 | * 描 述:
11 | * 修订历史:
12 | * ================================================
13 | */
14 |
15 | public class VideoEditInfo implements Serializable {
16 |
17 | public String path; //图片的sd卡路径
18 | public long time;//图片所在视频的时间 毫秒
19 |
20 | public VideoEditInfo() {
21 | }
22 |
23 |
24 | @Override
25 | public String toString() {
26 | return "VideoEditInfo{" +
27 | "path='" + path + '\'' +
28 | ", time='" + time + '\'' +
29 | '}';
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/gxz/example/videoedit/VideoExtractFrameAsyncUtils.java:
--------------------------------------------------------------------------------
1 | package com.gxz.example.videoedit;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.Matrix;
5 | import android.media.MediaMetadataRetriever;
6 | import android.os.Handler;
7 | import android.os.Message;
8 | import android.util.Log;
9 |
10 | /**
11 | * ================================================
12 | * 作 者:顾修忠-guxiuzhong@youku.com/gfj19900401@163.com
13 | * 版 本:
14 | * 创建日期:2017/3/2-下午7:06
15 | * 描 述:
16 | * 修订历史:
17 | * ================================================
18 | */
19 |
20 | public class VideoExtractFrameAsyncUtils {
21 |
22 | private Handler mHandler;
23 | private int extractW;
24 | private int extractH;
25 |
26 | public VideoExtractFrameAsyncUtils(int extractW, int extractH, Handler mHandler) {
27 | this.mHandler = mHandler;
28 | this.extractW=extractW;
29 | this.extractH=extractH;
30 | }
31 |
32 | public void getVideoThumbnailsInfoForEdit(String videoPath, String OutPutFileDirPath, long startPosition, long endPosition, int thumbnailsCount) {
33 | MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
34 | metadataRetriever.setDataSource(videoPath);
35 | long interval = (endPosition - startPosition) / (thumbnailsCount - 1);
36 | for (int i = 0; i < thumbnailsCount; i++) {
37 | if (stop) {
38 | Log.d("ExtractFrame", "-------ok-stop-stop-->>>>>>>>>");
39 | metadataRetriever.release();
40 | break;
41 | }
42 | long time = startPosition + interval * i;
43 | if (i == thumbnailsCount - 1) {
44 | if (interval > 1000) {
45 | String path = extractFrame(metadataRetriever, endPosition - 800, OutPutFileDirPath);
46 | sendAPic(path, endPosition - 800);
47 | } else {
48 | String path = extractFrame(metadataRetriever, endPosition, OutPutFileDirPath);
49 | sendAPic(path, endPosition);
50 | }
51 | } else {
52 | String path = extractFrame(metadataRetriever, time, OutPutFileDirPath);
53 | sendAPic(path, time);
54 | }
55 | }
56 | metadataRetriever.release();
57 | }
58 |
59 | /**
60 | * 成功一张add一张
61 | *
62 | * @param path path
63 | * @param time time
64 | */
65 | private void sendAPic(String path, long time) {
66 | VideoEditInfo info = new VideoEditInfo();
67 | info.path = path;
68 | info.time = time;
69 | Message msg = mHandler.obtainMessage(ExtractFrameWorkThread.MSG_SAVE_SUCCESS);
70 | msg.obj = info;
71 | mHandler.sendMessage(msg);
72 | }
73 |
74 | private String extractFrame(MediaMetadataRetriever metadataRetriever, long time, String OutPutFileDirPath) {
75 | Bitmap bitmap = metadataRetriever.getFrameAtTime(time * 1000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
76 | if (bitmap != null) {
77 | Bitmap bitmapNew= scaleImage(bitmap);
78 | String path= PictureUtils.saveImageToSDForEdit(bitmapNew, OutPutFileDirPath, System.currentTimeMillis() + "_" + time + PictureUtils.POSTFIX);
79 | if (bitmapNew!=null &&!bitmapNew.isRecycled()) {
80 | bitmapNew.recycle();
81 | bitmapNew = null;
82 | }
83 | return path;
84 | }
85 | return null;
86 | }
87 |
88 | /**
89 | * 设置固定的宽度,高度随之变化,使图片不会变形
90 | *
91 | * @param bm Bitmap
92 | * @return Bitmap
93 | */
94 | private Bitmap scaleImage(Bitmap bm) {
95 | if (bm == null) {
96 | return null;
97 | }
98 | int width = bm.getWidth();
99 | int height = bm.getHeight();
100 | float scaleWidth = extractW * 1.0f / width;
101 | // float scaleHeight =extractH*1.0f / height;
102 | Matrix matrix = new Matrix();
103 | matrix.postScale(scaleWidth, scaleWidth);
104 | Bitmap newBm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix,
105 | true);
106 | if (!bm.isRecycled()) {
107 | bm.recycle();
108 | bm = null;
109 | }
110 | return newBm;
111 | }
112 |
113 |
114 | private volatile boolean stop;
115 |
116 | public void stopExtract() {
117 | stop = true;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/handle_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/app/src/main/res/drawable-hdpi/handle_left.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/lf_ugc_publish_pos.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/upload_overlay_black.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/app/src/main/res/drawable-hdpi/upload_overlay_black.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/upload_overlay_trans.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/app/src/main/res/drawable-hdpi/upload_overlay_trans.9.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
21 |
22 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_video_edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
24 |
25 |
26 |
27 |
34 |
35 |
40 |
41 |
49 |
50 |
54 |
55 |
59 |
60 |
67 |
68 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/video_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/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 | VideoEdit
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/gxz/example/videoedit/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.gxz.example.videoedit;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.3.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/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/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Apr 10 23:11:01 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-3.3-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 |
--------------------------------------------------------------------------------
/pic1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/pic1.png
--------------------------------------------------------------------------------
/pic2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/pic2.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/sr-new.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ta893115871/VideoEdit/3c93e3c3ba17d0576ff6bcd6d0fca3f3350efe34/sr-new.mp4
--------------------------------------------------------------------------------