├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── dictionaries │ └── moos.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── LocalMediaSelector ├── .gitignore ├── .idea │ ├── caches │ │ └── build_file_checksums.ser │ ├── codeStyles │ │ └── Project.xml │ ├── dictionaries │ │ └── moos.xml │ ├── gradle.xml │ ├── misc.xml │ └── runConfigurations.xml ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── moos │ │ │ └── media │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── moos │ │ │ │ └── media │ │ │ │ ├── adapter │ │ │ │ ├── LocalImageAdapter.kt │ │ │ │ └── LocalVideoAdapter.kt │ │ │ │ ├── entity │ │ │ │ ├── BaseMediaEntity.java │ │ │ │ └── impl │ │ │ │ │ ├── ImageMediaEntity.java │ │ │ │ │ └── VideoMediaEntity.java │ │ │ │ ├── ui │ │ │ │ ├── ImageSelectActivity.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── MediaItemDecoration.java │ │ │ │ └── VideoSelectActivity.kt │ │ │ │ └── utils │ │ │ │ └── MediaUtils.kt │ │ └── res │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable-xxhdpi │ │ │ ├── radio_off.png │ │ │ └── radio_on.png │ │ │ ├── drawable │ │ │ ├── ic_launcher_background.xml │ │ │ ├── image_icon.png │ │ │ ├── radio_selector.xml │ │ │ └── video_icon.png │ │ │ ├── layout │ │ │ ├── activity_image_select.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_video_select.xml │ │ │ └── rv_item_local_video_layout.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.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 │ │ └── moos │ │ └── media │ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── image │ ├── art_customize_ui.png │ ├── art_select_image.png │ └── art_select_video.png └── settings.gradle └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/dictionaries/moos.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Moos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LocalMediaSelector/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /LocalMediaSelector/.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /LocalMediaSelector/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /LocalMediaSelector/.idea/dictionaries/moos.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LocalMediaSelector/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /LocalMediaSelector/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /LocalMediaSelector/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /LocalMediaSelector/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /LocalMediaSelector/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 27 7 | defaultConfig { 8 | applicationId "com.moos.media" 9 | minSdkVersion 21 10 | targetSdkVersion 27 11 | versionCode 1 12 | versionName "1.0" 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation fileTree(dir: 'libs', include: ['*.jar']) 25 | implementation 'com.android.support:appcompat-v7:27.1.0' 26 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 27 | testImplementation 'junit:junit:4.12' 28 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 29 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 30 | implementation 'com.android.support:support-v4:27.1.0' 31 | implementation 'com.android.support:cardview-v7:27.1.0' 32 | implementation 'com.android.support:design:27.1.0' 33 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 34 | implementation "com.github.bumptech.glide:glide:4.8.0" 35 | implementation "org.jetbrains.anko:anko-commons:0.10.5" 36 | } 37 | repositories { 38 | mavenCentral() 39 | } 40 | -------------------------------------------------------------------------------- /LocalMediaSelector/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /LocalMediaSelector/app/src/androidTest/java/com/moos/media/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.moos.media; 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 | * Instrumented 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() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.moos.media", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LocalMediaSelector/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LocalMediaSelector/app/src/main/java/com/moos/media/adapter/LocalImageAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.ucard.timeory.adapter 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.CheckBox 9 | import android.widget.ImageView 10 | import android.widget.RelativeLayout 11 | import com.bumptech.glide.Glide 12 | import com.bumptech.glide.load.engine.DiskCacheStrategy 13 | import com.bumptech.glide.request.RequestOptions 14 | import com.moos.media.R 15 | import com.moos.media.entity.impl.ImageMediaEntity 16 | import org.jetbrains.anko.find 17 | 18 | /** 19 | *
 20 |  *    author: moosphon
 21 |  *    date:   2018/09/16
 22 |  *    desc:   本地视频的适配器
 23 |  * 
 24 |  */
 25 | class LocalImageAdapter: RecyclerView.Adapter() {
 26 |     lateinit var context: Context
 27 |     private var mSelectedPosition: Int = 0
 28 |     var listener: OnLocalImageSelectListener? = null
 29 |     private lateinit var data: List
 30 |     /** 存储选中的图片 */
 31 |     private var chosenImages : HashMap  = HashMap()
 32 |     /** 存储选中的状态 */
 33 |     private var checkStates  : HashMap = HashMap()
 34 | 
 35 | 
 36 |     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LocalImageViewHolder {
 37 |         context = parent.context
 38 |         val view = LayoutInflater.from(parent.context).inflate(R.layout.rv_item_local_video_layout, parent, false)
 39 |         return LocalImageViewHolder(view)
 40 |     }
 41 | 
 42 |     override fun getItemCount(): Int {
 43 |         return data.size
 44 |     }
 45 | 
 46 |     override fun onBindViewHolder(holder: LocalImageViewHolder, position: Int) {
 47 |         val thumbnailImage: ImageView = holder.view.find(R.id.local_video_item_thumbnail)
 48 |         val checkBox: CheckBox = holder.view.find(R.id.local_video_item_cb)
 49 |         /** 通过map存储checkbox选中状态,放置rv复用机制导致的状态混乱状态 */
 50 |         checkBox.setOnCheckedChangeListener(null)
 51 |         checkBox.isChecked = checkStates.containsKey(position)
 52 |         val options = RequestOptions()
 53 |                 .diskCacheStrategy(DiskCacheStrategy.NONE)
 54 |                 .error(R.mipmap.ic_launcher)
 55 |                 .placeholder(R.mipmap.ic_launcher)
 56 | 
 57 |         Glide.with(context)
 58 |                 .asBitmap()
 59 |                 .load(data[position].thumbnailPath)
 60 |                 .apply(options)
 61 |                 .thumbnail(0.2f)
 62 |                 .into(thumbnailImage)
 63 |         checkBox.setOnCheckedChangeListener{
 64 |             _, isChecked ->
 65 |             if (isChecked){
 66 |                 checkStates[position] = true
 67 | 
 68 |                 data[position].isSelected = true
 69 |                 // 将当前选中的图片存入map
 70 |                 chosenImages[position] = data[position].path
 71 | 
 72 |             }else{
 73 |                 // 从选中列表中移除
 74 |                 checkStates.remove(position)
 75 |                 chosenImages.remove(position)
 76 |             }
 77 |             if (listener != null){
 78 |                 val selectedImages  = ArrayList()
 79 |                 for (v in chosenImages.values){
 80 |                     selectedImages.add(v)
 81 |                 }
 82 |                 listener!!.onImageSelect(holder.view, position, selectedImages)
 83 | 
 84 |             }
 85 |         }
 86 | 
 87 | 
 88 |     }
 89 | 
 90 |     fun setData(data: List){
 91 |         this.data = data
 92 |         for (i in 0 until data.size) {
 93 |             if (data[i].isSelected) {
 94 |                 mSelectedPosition = i
 95 |             }
 96 |         }
 97 |     }
 98 | 
 99 | 
100 | 
101 |     class LocalImageViewHolder(val view: View) : RecyclerView.ViewHolder(view)
102 |     /** 自定义的本地视频选择监听器 */
103 |     interface OnLocalImageSelectListener{
104 |         fun onImageSelect(view: View, position:Int, images: List)
105 |     }
106 | 
107 | }


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/java/com/moos/media/adapter/LocalVideoAdapter.kt:
--------------------------------------------------------------------------------
  1 | package com.ucard.timeory.adapter
  2 | 
  3 | import android.content.Context
  4 | import android.net.Uri
  5 | import android.support.v7.widget.RecyclerView
  6 | import android.view.LayoutInflater
  7 | import android.view.View
  8 | import android.view.ViewGroup
  9 | import android.widget.CheckBox
 10 | import android.widget.FrameLayout
 11 | import android.widget.ImageView
 12 | import android.widget.RelativeLayout
 13 | import com.bumptech.glide.Glide
 14 | import com.bumptech.glide.load.engine.DiskCacheStrategy
 15 | import com.bumptech.glide.request.RequestOptions
 16 | import com.moos.media.R
 17 | import com.moos.media.entity.impl.VideoMediaEntity
 18 | import org.jetbrains.anko.find
 19 | import java.io.File
 20 | 
 21 | /**
 22 |  * 
 23 |  *    author: moosphon
 24 |  *    date:   2018/09/16
 25 |  *    desc:   本地视频的适配器
 26 |  * 
 27 |  */
 28 | class LocalVideoAdapter: RecyclerView.Adapter() {
 29 |     lateinit var context: Context
 30 |     private var mSelectedPosition: Int = -1
 31 |     var listener: OnLocalVideoSelectListener? = null
 32 |     private lateinit var data: List
 33 |     private var checkState: HashSet = HashSet()
 34 | 
 35 |     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LocalVideoViewHolder {
 36 |         context = parent.context
 37 |         val view = LayoutInflater.from(parent.context).inflate(R.layout.rv_item_local_video_layout, parent, false)
 38 |         return LocalVideoViewHolder(view)
 39 |     }
 40 | 
 41 |     override fun getItemCount(): Int {
 42 |         return data.size
 43 |     }
 44 | 
 45 |     override fun onBindViewHolder(holder: LocalVideoViewHolder, position: Int) {
 46 |         val thumbnailImage: ImageView = holder.view.find(R.id.local_video_item_thumbnail)
 47 |         val checkBox: CheckBox = holder.view.find(R.id.local_video_item_cb)
 48 |         checkBox.isChecked = checkState.contains(position)
 49 |         val options = RequestOptions()
 50 |                 .diskCacheStrategy(DiskCacheStrategy.NONE)
 51 |                 .error(R.mipmap.ic_launcher)
 52 |                 .placeholder(R.mipmap.ic_launcher)
 53 | 
 54 | 
 55 |         Glide.with(context)
 56 |                 .asBitmap()
 57 |                 .load(data[position].path)
 58 |                 .apply(options)
 59 |                 .thumbnail(0.2f)
 60 |                 .into(thumbnailImage)
 61 |         checkBox.setOnClickListener {
 62 | 
 63 |             if (mSelectedPosition!=position){
 64 |                 //先取消上个item的勾选状态
 65 |                 checkState.remove(mSelectedPosition)
 66 |                 notifyItemChanged(mSelectedPosition)
 67 |                 //设置新Item的勾选状态
 68 |                 mSelectedPosition = position
 69 |                 checkState.add(mSelectedPosition)
 70 |                 notifyItemChanged(mSelectedPosition)
 71 |             }else if(checkBox.isChecked){
 72 |                 checkState.add(position)
 73 | 
 74 |             }else if(!checkBox.isChecked){
 75 | 
 76 |                 checkState.remove(position)
 77 |             }
 78 |             if (listener != null){
 79 |                 listener!!.onVideoSelect(holder.view, position)
 80 | 
 81 |             }
 82 |         }
 83 |     }
 84 | 
 85 |     fun setData(data: List){
 86 |         this.data = data
 87 |         for (i in 0 until data.size) {
 88 |             if (data[i].isSelected) {
 89 |                 mSelectedPosition = i
 90 |             }
 91 |         }
 92 |     }
 93 | 
 94 | 
 95 | 
 96 | 
 97 | 
 98 |     class LocalVideoViewHolder(val view: View) : RecyclerView.ViewHolder(view)
 99 |     /** 自定义的本地视频选择监听器 */
100 |     interface OnLocalVideoSelectListener{
101 |         fun onVideoSelect(view:View, position:Int)
102 |     }
103 | 
104 | }


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/java/com/moos/media/entity/BaseMediaEntity.java:
--------------------------------------------------------------------------------
 1 | package com.moos.media.entity;
 2 | 
 3 | import android.os.Parcel;
 4 | import android.os.Parcelable;
 5 | 
 6 | /**
 7 |  * base entity data for local media
 8 |  *
 9 |  * @author Moosphon
10 |  */
11 | public abstract class BaseMediaEntity implements Parcelable{
12 |     protected enum TYPE{
13 |         IMAGE,
14 |         VIDEO
15 |     }
16 | 
17 |     protected String path;
18 |     protected String id;
19 |     protected String size;
20 |     public Boolean isSelected = false;
21 | 
22 | 
23 |     public BaseMediaEntity() {
24 | 
25 |     }
26 | 
27 |     public BaseMediaEntity(String path, String id) {
28 |         this.path = path;
29 |         this.id = id;
30 |     }
31 | 
32 |     public BaseMediaEntity(Parcel in) {
33 |         this.path = in.readString();
34 |         this.id   = in.readString();
35 |         this.size = in.readString();
36 |     }
37 | 
38 |     public abstract TYPE getMediaType();
39 | 
40 |     public String getPath() {
41 |         return path;
42 |     }
43 | 
44 |     public void setPath(String path) {
45 |         this.path = path;
46 |     }
47 | 
48 |     public String getId() {
49 |         return id;
50 |     }
51 | 
52 |     public void setId(String id) {
53 |         this.id = id;
54 |     }
55 | 
56 |     public String getSize() {
57 |         return size;
58 |     }
59 | 
60 |     public void setSize(String size) {
61 |         this.size = size;
62 |     }
63 | 
64 |     public Boolean getSelected() {
65 |         return isSelected;
66 |     }
67 | 
68 |     public void setSelected(Boolean selected) {
69 |         isSelected = selected;
70 |     }
71 | 
72 |     @Override
73 |     public int describeContents() {
74 |         return 0;
75 |     }
76 | 
77 |     @Override
78 |     public void writeToParcel(Parcel dest, int flags) {
79 |         dest.writeString(this.path);
80 |         dest.writeString(this.id);
81 |         dest.writeString(this.size);
82 |     }
83 | }
84 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/java/com/moos/media/entity/impl/ImageMediaEntity.java:
--------------------------------------------------------------------------------
  1 | package com.moos.media.entity.impl;
  2 | 
  3 | import android.os.Parcel;
  4 | import android.support.annotation.NonNull;
  5 | import android.text.TextUtils;
  6 | 
  7 | import com.moos.media.entity.BaseMediaEntity;
  8 | 
  9 | import java.io.File;
 10 | 
 11 | /**
 12 |  * image entity base on {@link BaseMediaEntity}
 13 |  *
 14 |  * @author moosphon
 15 |  */
 16 | public class ImageMediaEntity extends BaseMediaEntity {
 17 | 
 18 |     private static final long MAX_GIF_SIZE = 1024 * 1024L;
 19 |     private static final long MAX_IMAGE_SIZE = 1024 * 1024L;
 20 | 
 21 |     private String mThumbnailPath;
 22 |     private String mCompressPath;
 23 |     private int mHeight;
 24 |     private int mWidth;
 25 |     private IMAGE_TYPE mImageType;
 26 |     private String mMimeType;
 27 | 
 28 | 
 29 |     public enum IMAGE_TYPE {
 30 |         PNG, JPG, GIF
 31 |     }
 32 | 
 33 |     public ImageMediaEntity(String id, String imagePath) {
 34 |         super(id, imagePath);
 35 |     }
 36 | 
 37 |     public ImageMediaEntity(@NonNull File file) {
 38 |         this.id = String.valueOf(System.currentTimeMillis());
 39 |         this.path = file.getAbsolutePath();
 40 |         this.size = String.valueOf(file.length());
 41 |     }
 42 | 
 43 |     public ImageMediaEntity(Builder builder) {
 44 |         super(builder.mImagePath, builder.mId);
 45 |         this.mThumbnailPath = builder.mThumbnailPath;
 46 |         this.size = builder.mSize;
 47 |         this.mHeight = builder.mHeight;
 48 |         this.mWidth = builder.mWidth;
 49 |         this.mMimeType = builder.mMimeType;
 50 |         this.mImageType = getImageTypeByMime(builder.mMimeType);
 51 |     }
 52 | 
 53 |     @Override
 54 |     public TYPE getMediaType() {
 55 |         return TYPE.IMAGE;
 56 |     }
 57 | 
 58 | 
 59 |     public boolean isGifOverSize() {
 60 |         return isGif() && Long.valueOf(getSize()) > MAX_GIF_SIZE;
 61 |     }
 62 | 
 63 |     public boolean isGif() {
 64 |         return getImageType() == IMAGE_TYPE.GIF;
 65 |     }
 66 | 
 67 | 
 68 |     /**
 69 |      * get mime type displayed in database.
 70 |      *
 71 |      * @return "image/gif" or "image/jpeg".
 72 |      */
 73 |     public String getMimeType() {
 74 |         if (getImageType() == IMAGE_TYPE.GIF) {
 75 |             return "image/gif";
 76 |         } else if (getImageType() == IMAGE_TYPE.JPG) {
 77 |             return "image/jpeg";
 78 |         }
 79 |         return "image/jpeg";
 80 |     }
 81 | 
 82 |     public IMAGE_TYPE getImageType() {
 83 |         return mImageType;
 84 |     }
 85 | 
 86 |     private IMAGE_TYPE getImageTypeByMime(String mimeType) {
 87 |         if (!TextUtils.isEmpty(mimeType)) {
 88 |             if ("image/gif".equals(mimeType)) {
 89 |                 return IMAGE_TYPE.GIF;
 90 |             } else if ("image/png".equals(mimeType)) {
 91 |                 return IMAGE_TYPE.PNG;
 92 |             } else {
 93 |                 return IMAGE_TYPE.JPG;
 94 |             }
 95 |         }
 96 |         return IMAGE_TYPE.PNG;
 97 |     }
 98 | 
 99 |     public void setImageType(IMAGE_TYPE imageType) {
100 |         mImageType = imageType;
101 |     }
102 | 
103 |     public String getId() {
104 |         return id;
105 |     }
106 | 
107 |     public int getHeight() {
108 |         return mHeight;
109 |     }
110 | 
111 |     public int getWidth() {
112 |         return mWidth;
113 |     }
114 | 
115 |     public String getCompressPath() {
116 |         return mCompressPath;
117 |     }
118 | 
119 | 
120 |     public void setCompressPath(String compressPath) {
121 |         mCompressPath = compressPath;
122 |     }
123 | 
124 |     public void setSize(String size) {
125 |         size = size;
126 |     }
127 | 
128 |     public void setHeight(int height) {
129 |         mHeight = height;
130 |     }
131 | 
132 |     public void setWidth(int width) {
133 |         mWidth = width;
134 |     }
135 | 
136 |     @Override
137 |     public String toString() {
138 |         return "ImageMedia{" +
139 |                 ", mThumbnailPath='" + mThumbnailPath + '\'' +
140 |                 ", mCompressPath='" + mCompressPath + '\'' +
141 |                 ", mSize='" + size + '\'' +
142 |                 ", mHeight=" + mHeight +
143 |                 ", mWidth=" + mWidth;
144 |     }
145 | 
146 |     @Override
147 |     public int hashCode() {
148 |         int result = id.hashCode();
149 |         result = 31 * result + (path != null ? path.hashCode() : 0);
150 |         return result;
151 |     }
152 | 
153 |     @NonNull
154 |     public String getThumbnailPath() {
155 |         if (isFileValid(mThumbnailPath)) {
156 |             return mThumbnailPath;
157 |         } else if (isFileValid(mCompressPath)) {
158 |             return mCompressPath;
159 |         }
160 |         return path;
161 |     }
162 | 
163 | 
164 |     @Override
165 |     public boolean equals(Object obj) {
166 |         if (this == obj) {
167 |             return true;
168 |         }
169 |         if (obj == null) {
170 |             return false;
171 |         }
172 |         if (getClass() != obj.getClass()) {
173 |             return false;
174 |         }
175 |         final ImageMediaEntity other = (ImageMediaEntity) obj;
176 |         return !(TextUtils.isEmpty(path) || TextUtils.isEmpty(other.path)) && this.path.equals(other.path);
177 |     }
178 | 
179 |     public static class Builder {
180 |         private String mId;
181 |         private String mImagePath;
182 |         private String mThumbnailPath;
183 |         private String mSize;
184 |         private int mHeight;
185 |         private int mWidth;
186 |         private String mMimeType;
187 | 
188 |         public Builder(String id, String path) {
189 |             this.mId = id;
190 |             this.mImagePath = path;
191 |         }
192 | 
193 |         public Builder setThumbnailPath(String thumbnailPath) {
194 |             mThumbnailPath = thumbnailPath;
195 |             return this;
196 |         }
197 | 
198 |         public Builder setHeight(int height) {
199 |             mHeight = height;
200 |             return this;
201 |         }
202 | 
203 |         public Builder setWidth(int width) {
204 |             mWidth = width;
205 |             return this;
206 |         }
207 | 
208 |         public Builder setMimeType(String mimeType) {
209 |             mMimeType = mimeType;
210 |             return this;
211 |         }
212 | 
213 |         public Builder setSize(String size) {
214 |             this.mSize = size;
215 |             return this;
216 |         }
217 | 
218 |         public ImageMediaEntity build() {
219 |             return new ImageMediaEntity(this);
220 |         }
221 |     }
222 | 
223 |     @Override
224 |     public int describeContents() {
225 |         return 0;
226 |     }
227 | 
228 |     @Override
229 |     public void writeToParcel(Parcel dest, int flags) {
230 |         super.writeToParcel(dest, flags);
231 |         dest.writeString(this.mThumbnailPath);
232 |         dest.writeString(this.mCompressPath);
233 |         dest.writeInt(this.mHeight);
234 |         dest.writeInt(this.mWidth);
235 |         dest.writeInt(this.mImageType == null ? -1 : this.mImageType.ordinal());
236 |         dest.writeString(this.mMimeType);
237 |     }
238 | 
239 |     protected ImageMediaEntity(Parcel in) {
240 |         super(in);
241 |         this.mThumbnailPath = in.readString();
242 |         this.mCompressPath = in.readString();
243 |         this.mHeight = in.readInt();
244 |         this.mWidth = in.readInt();
245 |         int tmpMImageType = in.readInt();
246 |         this.mImageType = tmpMImageType == -1 ? null : IMAGE_TYPE.values()[tmpMImageType];
247 |         this.mMimeType = in.readString();
248 |     }
249 | 
250 |     public static final Creator CREATOR = new Creator() {
251 |         @Override
252 |         public ImageMediaEntity createFromParcel(Parcel source) {
253 |             return new ImageMediaEntity(source);
254 |         }
255 | 
256 |         @Override
257 |         public ImageMediaEntity[] newArray(int size) {
258 |             return new ImageMediaEntity[size];
259 |         }
260 |     };
261 | 
262 |     private boolean isFileValid(String path) {
263 |         if (TextUtils.isEmpty(path)) {
264 |             return false;
265 |         }
266 |         File file = new File(path);
267 |         return isFileValid(file);
268 |     }
269 | 
270 |     boolean isFileValid(File file) {
271 |         return file != null && file.exists() && file.isFile() && file.length() > 0 && file.canRead();
272 |     }
273 | 
274 | 
275 | 
276 | }
277 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/java/com/moos/media/entity/impl/VideoMediaEntity.java:
--------------------------------------------------------------------------------
  1 | package com.moos.media.entity.impl;
  2 | 
  3 | import android.os.Parcel;
  4 | import android.os.Parcelable;
  5 | 
  6 | import com.moos.media.entity.BaseMediaEntity;
  7 | 
  8 | import java.util.Locale;
  9 | 
 10 | /**
 11 |  * video entity base on {@link BaseMediaEntity}
 12 |  *
 13 |  * @author moosphon
 14 |  */
 15 | public class VideoMediaEntity extends BaseMediaEntity {
 16 | 
 17 |     private static final long MB = 1024 * 1024;
 18 | 
 19 |     private String mTitle;
 20 |     private String mDuration;
 21 |     private String mDateTaken;
 22 |     private String mMimeType;
 23 | 
 24 |     private VideoMediaEntity() {
 25 |     }
 26 | 
 27 |     @Override
 28 |     public TYPE getMediaType() {
 29 |         return TYPE.VIDEO;
 30 |     }
 31 | 
 32 |     public VideoMediaEntity(Builder builder) {
 33 |         super(builder.mPath, builder.mId);
 34 |         this.mTitle = builder.mTitle;
 35 |         this.mDuration = builder.mDuration;
 36 |         this.size = builder.mSize;
 37 |         this.mDateTaken = builder.mDateTaken;
 38 |         this.mMimeType = builder.mMimeType;
 39 |     }
 40 | 
 41 |     public String getDuration() {
 42 |         try {
 43 |             long duration = Long.parseLong(mDuration);
 44 |             return formatTimeWithMin(duration);
 45 |         } catch (NumberFormatException e) {
 46 |             return "0:00";
 47 |         }
 48 |     }
 49 | 
 50 |     public String formatTimeWithMin(long duration) {
 51 |         if (duration <= 0) {
 52 |             return String.format(Locale.US, "%02d:%02d", 0, 0);
 53 |         }
 54 |         long totalSeconds = duration / 1000;
 55 | 
 56 |         long seconds = totalSeconds % 60;
 57 |         long minutes = (totalSeconds / 60) % 60;
 58 |         long hours = totalSeconds / 3600;
 59 | 
 60 |         if (hours > 0) {
 61 |             return String.format(Locale.US, "%02d:%02d", hours * 60 + minutes,
 62 |                     seconds);
 63 |         } else {
 64 |             return String.format(Locale.US, "%02d:%02d", minutes, seconds);
 65 |         }
 66 |     }
 67 | 
 68 |     public void setTitle(String title) {
 69 |         mTitle = title;
 70 |     }
 71 | 
 72 |     public void setDuration(String duration) {
 73 |         mDuration = duration;
 74 |     }
 75 | 
 76 |     public String getTitle() {
 77 |         return mTitle;
 78 |     }
 79 | 
 80 |     public String getSizeByUnit() {
 81 |         String sizeString = getSize();
 82 |         double size = Double.valueOf(sizeString);
 83 |         if (size == 0) {
 84 |             return "0K";
 85 |         }
 86 |         if (size >= MB) {
 87 |             double sizeInM = size / MB;
 88 |             return String.format(Locale.getDefault(), "%.1f", sizeInM) + "M";
 89 |         }
 90 |         double sizeInK = size / 1024;
 91 |         return String.format(Locale.getDefault(), "%.1f", sizeInK) + "K";
 92 |     }
 93 | 
 94 |     public String getDateTaken() {
 95 |         return mDateTaken;
 96 |     }
 97 | 
 98 |     public String getMimeType() {
 99 |         return mMimeType;
100 |     }
101 | 
102 | 
103 |     public static class Builder {
104 |         private String mId;
105 |         private String mTitle;
106 |         private String mPath;
107 |         private String mDuration;
108 |         private String mSize;
109 |         private String mDateTaken;
110 |         private String mMimeType;
111 | 
112 |         public Builder(String id, String path) {
113 |             this.mId = id;
114 |             this.mPath = path;
115 |         }
116 | 
117 |         public Builder setTitle(String title) {
118 |             this.mTitle = title;
119 |             return this;
120 |         }
121 | 
122 |         public Builder setDuration(String duration) {
123 |             this.mDuration = duration;
124 |             return this;
125 |         }
126 | 
127 |         public Builder setSize(String size) {
128 |             this.mSize = size;
129 |             return this;
130 |         }
131 | 
132 |         public Builder setDateTaken(String dateTaken) {
133 |             this.mDateTaken = dateTaken;
134 |             return this;
135 |         }
136 | 
137 |         public Builder setMimeType(String type) {
138 |             this.mMimeType = type;
139 |             return this;
140 |         }
141 | 
142 | 
143 |         public VideoMediaEntity build() {
144 |             return new VideoMediaEntity(this);
145 |         }
146 |     }
147 | 
148 |     @Override
149 |     public int describeContents() {
150 |         return 0;
151 |     }
152 | 
153 |     @Override
154 |     public void writeToParcel(Parcel dest, int flags) {
155 |         super.writeToParcel(dest, flags);
156 |         dest.writeString(this.mTitle);
157 |         dest.writeString(this.mDuration);
158 |         dest.writeString(this.mDateTaken);
159 |         dest.writeString(this.mMimeType);
160 |     }
161 | 
162 |     protected VideoMediaEntity(Parcel in) {
163 |         super(in);
164 |         this.mTitle = in.readString();
165 |         this.mDuration = in.readString();
166 |         this.mDateTaken = in.readString();
167 |         this.mMimeType = in.readString();
168 |     }
169 | 
170 |     public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
171 |         @Override
172 |         public VideoMediaEntity createFromParcel(Parcel source) {
173 |             return new VideoMediaEntity(source);
174 |         }
175 | 
176 |         @Override
177 |         public VideoMediaEntity[] newArray(int size) {
178 |             return new VideoMediaEntity[size];
179 |         }
180 |     };
181 | 
182 | }
183 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/java/com/moos/media/ui/ImageSelectActivity.kt:
--------------------------------------------------------------------------------
  1 | package com.moos.media.ui
  2 | 
  3 | import android.Manifest
  4 | import android.content.pm.PackageManager
  5 | import android.graphics.Color
  6 | import android.os.Build
  7 | import android.support.v7.app.AppCompatActivity
  8 | import android.os.Bundle
  9 | import android.os.Handler
 10 | import android.os.Message
 11 | import android.support.v4.app.ActivityCompat
 12 | import android.support.v4.content.ContextCompat
 13 | import android.support.v7.widget.GridLayoutManager
 14 | import android.util.Log
 15 | import android.view.View
 16 | import android.view.WindowManager
 17 | import com.moos.media.R
 18 | import com.moos.media.entity.impl.ImageMediaEntity
 19 | import com.ucard.timeory.adapter.LocalImageAdapter
 20 | import com.ucard.timeory.utils.MediaUtils
 21 | import kotlinx.android.synthetic.main.activity_image_select.*
 22 | import java.lang.ref.WeakReference
 23 | 
 24 | class ImageSelectActivity : AppCompatActivity(), LocalImageAdapter.OnLocalImageSelectListener {
 25 | 
 26 | 
 27 |     companion object {
 28 |         const val GET_LOCAL_IMAGES: Int = 100
 29 |         /**
 30 |          * by moosphon on 2018/09/16
 31 |          * desc: 解决handler内存泄漏的问题,消息的处理需要放在内部类的{@link #Handler.handleMessage}
 32 |          */
 33 |         private class WithoutLeakHandler( mActivity: ImageSelectActivity) : Handler(){
 34 |             private var weakReference: WeakReference = WeakReference(mActivity)
 35 | 
 36 |             override fun handleMessage(msg: Message) {
 37 |                 super.handleMessage(msg)
 38 |                 when(msg.what){
 39 |                     GET_LOCAL_IMAGES -> {
 40 |                         val activity = weakReference.get()
 41 | 
 42 |                         if (activity != null){
 43 |                             activity.adapter.setData(activity.imageData!!)
 44 |                             activity.rv_image.adapter = activity.adapter
 45 | 
 46 |                         }
 47 | 
 48 |                     }
 49 |                 }
 50 |             }
 51 |         }
 52 |     }
 53 |     private var imageData: List? = ArrayList()
 54 |     private var handler: Handler = WithoutLeakHandler(this)
 55 |     private val adapter = LocalImageAdapter()
 56 | 
 57 |     override fun onCreate(savedInstanceState: Bundle?) {
 58 |         super.onCreate(savedInstanceState)
 59 |         setContentView(R.layout.activity_image_select)
 60 |         initView()
 61 |     }
 62 | 
 63 |     private fun initView() {
 64 |         setSupportActionBar(tb_image)
 65 |         supportActionBar!!.setDisplayHomeAsUpEnabled(true)
 66 |         tb_image.setNavigationOnClickListener {
 67 |             onBackPressed()
 68 |         }
 69 |         getPermission()
 70 | 
 71 |         rv_image.layoutManager = GridLayoutManager(this, 3)
 72 |         rv_image.addItemDecoration(MediaItemDecoration(8, 3))
 73 |         adapter.listener = this
 74 | 
 75 | 
 76 |     }
 77 | 
 78 |     /** 获取存储权限 */
 79 |     private fun getPermission() {
 80 |         if (Build.VERSION.SDK_INT>22){
 81 |             if (ContextCompat.checkSelfPermission(this,
 82 |                             android.Manifest.permission.READ_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
 83 |                 ActivityCompat.requestPermissions(this,
 84 |                         arrayOf( Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE), 111)
 85 |             }else {
 86 |                 //已经获取到存储权限了
 87 |                 searchForLocalImages()
 88 |             }
 89 |         }else {
 90 |             //这个说明系统版本在6.0之下,不需要动态获取权限。
 91 |             searchForLocalImages()
 92 |         }
 93 | 
 94 |     }
 95 | 
 96 |     /**
 97 |      * by moosphon on 2018/09/15
 98 |      * desc: 搜索系统本地所有图片
 99 |      * use ContentResolver in {@link #MediaStore.Video} 
100 | */ 101 | private fun searchForLocalImages(){ 102 | Thread(Runnable { 103 | imageData = MediaUtils.getLocalPictures(this) 104 | Log.e("ImageSelectActivity", "扫描本地图片的数量为->"+imageData?.size) 105 | val message= Message() 106 | message.what = GET_LOCAL_IMAGES 107 | handler.sendMessage(message) 108 | }).start() 109 | } 110 | 111 | 112 | override fun onDestroy() { 113 | super.onDestroy() 114 | /** 消除内存泄漏隐患 */ 115 | handler.removeCallbacksAndMessages(null) 116 | } 117 | 118 | override fun onImageSelect(view: View, position: Int, images: List) { 119 | Log.e("ImageSelectActivity", "当前选中的图片为->"+images.toString()) 120 | } 121 | 122 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { 123 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 124 | if (requestCode == 111){ 125 | getPermission() 126 | } 127 | } 128 | 129 | 130 | } 131 | -------------------------------------------------------------------------------- /LocalMediaSelector/app/src/main/java/com/moos/media/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.moos.media.ui 2 | 3 | import android.support.v7.app.AppCompatActivity 4 | import android.os.Bundle 5 | import com.moos.media.R 6 | import kotlinx.android.synthetic.main.activity_main.* 7 | import org.jetbrains.anko.intentFor 8 | 9 | class MainActivity : AppCompatActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | setContentView(R.layout.activity_main) 14 | btn_image_select.setOnClickListener { 15 | startActivity(intentFor()) 16 | } 17 | 18 | btn_video_select.setOnClickListener { 19 | startActivity(intentFor()) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LocalMediaSelector/app/src/main/java/com/moos/media/ui/MediaItemDecoration.java: -------------------------------------------------------------------------------- 1 | package com.moos.media.ui; 2 | 3 | import android.graphics.Rect; 4 | import android.support.v7.widget.GridLayoutManager; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.support.v7.widget.StaggeredGridLayoutManager; 7 | import android.view.View; 8 | 9 | /** 10 | * the space of each media items{@link com.moos.media.entity.BaseMediaEntity } for rv 11 | * 12 | * @author moosphon 13 | */ 14 | public class MediaItemDecoration extends RecyclerView.ItemDecoration { 15 | private int mSpace; 16 | private int mSpanCount; 17 | private int mRadixX; 18 | private int mItemCountInLastLine; 19 | private int mOldItemCount = -1; 20 | 21 | public MediaItemDecoration(int space) { 22 | this(space, 1); 23 | } 24 | 25 | public MediaItemDecoration(int space, int spanCount) { 26 | this.mSpace = space; 27 | this.mSpanCount = spanCount; 28 | this.mRadixX = space / spanCount; 29 | } 30 | 31 | @Override 32 | public void getItemOffsets(Rect outRect, View view, final RecyclerView parent, RecyclerView.State state) { 33 | RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); 34 | final int sumCount = state.getItemCount(); 35 | final int position = params.getViewLayoutPosition(); 36 | final int spanSize; 37 | final int index; 38 | 39 | if (params instanceof GridLayoutManager.LayoutParams) { 40 | GridLayoutManager.LayoutParams gridParams = (GridLayoutManager.LayoutParams) params; 41 | spanSize = gridParams.getSpanSize(); 42 | index = gridParams.getSpanIndex(); 43 | 44 | if ((position == 0 || mOldItemCount != sumCount) && mSpanCount > 1) { 45 | int countInLine = 0; 46 | int spanIndex; 47 | 48 | for (int tempPosition = sumCount - mSpanCount; tempPosition < sumCount; tempPosition++) { 49 | spanIndex = ((GridLayoutManager) parent.getLayoutManager()).getSpanSizeLookup().getSpanIndex(tempPosition, mSpanCount); 50 | countInLine = spanIndex == 0 ? 1 : countInLine + 1; 51 | } 52 | mItemCountInLastLine = countInLine; 53 | if (mOldItemCount != sumCount) { 54 | mOldItemCount = sumCount; 55 | if (position != 0) { 56 | parent.post(new Runnable() { 57 | @Override 58 | public void run() { 59 | parent.invalidateItemDecorations(); 60 | } 61 | }); 62 | } 63 | } 64 | } 65 | } else if (params instanceof StaggeredGridLayoutManager.LayoutParams) { 66 | spanSize = ((StaggeredGridLayoutManager.LayoutParams) params).isFullSpan() ? mSpanCount : 1; 67 | index = ((StaggeredGridLayoutManager.LayoutParams) params).getSpanIndex(); 68 | } else { 69 | spanSize = 1; 70 | index = 0; 71 | } 72 | 73 | if (spanSize < 1 || index < 0 || spanSize > mSpanCount) { 74 | return; 75 | } 76 | 77 | outRect.left = mSpace - mRadixX * index; 78 | outRect.right = mRadixX + mRadixX * (index + spanSize - 1); 79 | 80 | if (mSpanCount == 1 && position == sumCount - 1) { 81 | outRect.bottom = mSpace; 82 | } else if (position >= sumCount - mItemCountInLastLine && position < sumCount) { 83 | outRect.bottom = mSpace; 84 | } 85 | outRect.top = mSpace; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /LocalMediaSelector/app/src/main/java/com/moos/media/ui/VideoSelectActivity.kt: -------------------------------------------------------------------------------- 1 | package com.moos.media.ui 2 | 3 | import android.Manifest 4 | import android.content.pm.PackageManager 5 | import android.os.Build 6 | import android.support.v7.app.AppCompatActivity 7 | import android.os.Bundle 8 | import android.os.Handler 9 | import android.os.Message 10 | import android.support.v4.app.ActivityCompat 11 | import android.support.v4.content.ContextCompat 12 | import android.support.v7.widget.GridLayoutManager 13 | import android.util.Log 14 | import android.view.View 15 | import com.moos.media.R 16 | import com.moos.media.entity.impl.ImageMediaEntity 17 | import com.moos.media.entity.impl.VideoMediaEntity 18 | import com.ucard.timeory.adapter.LocalImageAdapter 19 | import com.ucard.timeory.adapter.LocalVideoAdapter 20 | import com.ucard.timeory.utils.MediaUtils 21 | import kotlinx.android.synthetic.main.activity_video_select.* 22 | import java.lang.ref.WeakReference 23 | 24 | class VideoSelectActivity : AppCompatActivity(), LocalVideoAdapter.OnLocalVideoSelectListener { 25 | 26 | companion object { 27 | const val GET_LOCAL_VIDEOS: Int = 100 28 | /** 29 | * by moosphon on 2018/09/16 30 | * desc: 解决handler内存泄漏的问题,消息的处理需要放在内部类的{@link #Handler.handleMessage} 31 | */ 32 | private class WithoutLeakHandler( mActivity: VideoSelectActivity) : Handler(){ 33 | private var weakReference: WeakReference = WeakReference(mActivity) 34 | 35 | override fun handleMessage(msg: Message) { 36 | super.handleMessage(msg) 37 | when(msg.what){ 38 | GET_LOCAL_VIDEOS -> { 39 | val activity = weakReference.get() 40 | 41 | if (activity != null){ 42 | activity.adapter.setData(activity.videoData!!) 43 | activity.rv_video.adapter = activity.adapter 44 | 45 | } 46 | 47 | } 48 | } 49 | } 50 | } 51 | } 52 | private var videoData: List? = ArrayList() 53 | private var handler: Handler = WithoutLeakHandler(this) 54 | private val adapter = LocalVideoAdapter() 55 | private var currentVideo: String? = null 56 | 57 | override fun onCreate(savedInstanceState: Bundle?) { 58 | super.onCreate(savedInstanceState) 59 | setContentView(R.layout.activity_video_select) 60 | initView() 61 | } 62 | 63 | private fun initView() { 64 | setSupportActionBar(tb_video) 65 | supportActionBar!!.setDisplayHomeAsUpEnabled(true) 66 | tb_video.setNavigationOnClickListener { 67 | onBackPressed() 68 | } 69 | getPermission() 70 | rv_video.layoutManager = GridLayoutManager(this, 3) 71 | rv_video.addItemDecoration(MediaItemDecoration(8, 3)) 72 | adapter.listener = this 73 | 74 | } 75 | 76 | /** 获取存储权限 */ 77 | private fun getPermission() { 78 | if (Build.VERSION.SDK_INT>22){ 79 | if (ContextCompat.checkSelfPermission(this, 80 | android.Manifest.permission.READ_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){ 81 | ActivityCompat.requestPermissions(this, 82 | arrayOf( Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE), 111) 83 | }else { 84 | //已经获取到存储权限了 85 | searchForLocalVideos() 86 | } 87 | }else { 88 | //这个说明系统版本在6.0之下,不需要动态获取权限。 89 | searchForLocalVideos() 90 | } 91 | 92 | } 93 | 94 | /** 95 | * by moosphon on 2018/09/15 96 | * desc: 搜索系统本地所有视频 97 | * use ContentResolver in {@link #MediaStore.Video}
98 | */ 99 | private fun searchForLocalVideos(){ 100 | Thread(Runnable { 101 | videoData = MediaUtils.getLocalVideos(this) 102 | Log.e("VideoSelectActivity", "扫描本地视频的数量为->"+videoData?.size) 103 | val message= Message() 104 | message.what = GET_LOCAL_VIDEOS 105 | handler.sendMessage(message) 106 | }).start() 107 | } 108 | 109 | 110 | override fun onDestroy() { 111 | super.onDestroy() 112 | /** 消除内存泄漏隐患 */ 113 | handler.removeCallbacksAndMessages(null) 114 | } 115 | 116 | override fun onVideoSelect(view: View, position: Int) { 117 | currentVideo = videoData!![position].path 118 | Log.e("VideoSelectActivity", "当前选中的视频为->$currentVideo") 119 | } 120 | 121 | 122 | } 123 | -------------------------------------------------------------------------------- /LocalMediaSelector/app/src/main/java/com/moos/media/utils/MediaUtils.kt: -------------------------------------------------------------------------------- 1 | package com.ucard.timeory.utils 2 | 3 | import android.provider.MediaStore 4 | import android.content.ContentResolver.SCHEME_CONTENT 5 | import android.content.ContentResolver 6 | import android.content.Context 7 | import android.database.Cursor 8 | import android.net.Uri 9 | import com.moos.media.entity.impl.ImageMediaEntity 10 | import com.moos.media.entity.impl.VideoMediaEntity 11 | import java.util.ArrayList 12 | 13 | 14 | 15 | 16 | 17 | /* 18 | * Copyright (C) 2018 moosphon. 19 | * 20 | * Licensed under the Apache License, Version 2.0 (the "License"); 21 | * you may not use this file except in compliance with the License. 22 | * You may obtain a copy of the License at 23 | * 24 | * http://www.apache.org/licenses/LICENSE-2.0 25 | * 26 | * Unless required by applicable law or agreed to in writing, software 27 | * distributed under the License is distributed on an "AS IS" BASIS, 28 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29 | * See the License for the specific language governing permissions and 30 | * limitations under the License. 31 | */ 32 | class MediaUtils { 33 | /** 34 | * Get or deal with the media data of current phone. 35 | * more details visit framework document. 36 | */ 37 | 38 | companion object { 39 | 40 | private var mThumbnailMap: Map? = null 41 | 42 | /** 43 | *
 44 |          *     @author moosphon  (about me: https://github.com/Moosphan)
 45 |          *     @date   2018/09/16
 46 |          *     @desc   get the origin path for local pictures.
 47 |          * 
 48 |          */
 49 |         fun getPicturePath(resolver: ContentResolver, uri: Uri?): String? {
 50 |             if (uri == null) {
 51 |                 return null
 52 |             }
 53 | 
 54 |             if (SCHEME_CONTENT == uri.getScheme()) {
 55 |                 var cursor: Cursor? = null
 56 |                 try {
 57 |                     cursor = resolver.query(uri, arrayOf(MediaStore.Images.ImageColumns.DATA), null, null, null)
 58 |                     return if (cursor == null || !cursor.moveToFirst()) {
 59 |                         null
 60 |                     } else cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA))
 61 |                 } finally {
 62 |                     if (cursor != null) {
 63 |                         cursor.close()
 64 |                     }
 65 |                 }
 66 |             }
 67 |             return uri.getPath()
 68 |         }
 69 | 
 70 |         /**
 71 |          * 
 72 |          *     @author moosphon  (about me: https://github.com/Moosphan)
 73 |          *     @date   2018/09/16
 74 |          *     @desc   get all pictures of the phone.
 75 |          * 
 76 |          */
 77 |         fun getLocalPictures(mContext: Context?): List? {
 78 |             val images = ArrayList()
 79 |             val resolver = mContext?.contentResolver
 80 |             var cursor: Cursor? = null
 81 |             queryImageThumbnails(resolver!!, arrayOf(MediaStore.Images.Thumbnails.IMAGE_ID, MediaStore.Images.Thumbnails.DATA))
 82 |             try {
 83 |                 cursor = resolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
 84 |                         arrayOf(MediaStore.Images.ImageColumns.DATA,
 85 |                                 MediaStore.Images.ImageColumns._ID,
 86 |                                 MediaStore.Images.ImageColumns.SIZE,
 87 |                                 MediaStore.Images.ImageColumns.MIME_TYPE),
 88 |                         null, null, null)
 89 |                 return if (cursor == null || !cursor.moveToFirst()) {
 90 |                     null
 91 |                 } else {
 92 |                     do {
 93 |                         val picPath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA))
 94 |                         val id = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID))
 95 |                         val size = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.SIZE))
 96 |                         val mimeType = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE))
 97 |                         val image = ImageMediaEntity.Builder(id, picPath)
 98 |                                 .setMimeType(mimeType)
 99 |                                 .setSize(size)
100 |                                 .setThumbnailPath(mThumbnailMap?.get(id))
101 |                                 .build()
102 |                         //images.add(cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA)))
103 |                         images.add(image)
104 |                         mThumbnailMap = null
105 |                     }while (cursor.moveToNext())
106 | 
107 |                     return images
108 |                 }
109 |             } finally {
110 |                 if (cursor != null) {
111 |                     cursor.close()
112 |                 }
113 |             }
114 |         }
115 | 
116 |         /**
117 |          * search for thumbnails for local images
118 |          *
119 |          * @author moosphon
120 |          */
121 |         private fun queryImageThumbnails(cr: ContentResolver, projection: Array) {
122 |             var cur: Cursor? = null
123 |             try {
124 |                 cur = MediaStore.Images.Thumbnails.queryMiniThumbnails(cr, MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI,
125 |                         MediaStore.Images.Thumbnails.MINI_KIND, projection)
126 |                 if (cur != null && cur.moveToFirst()) {
127 |                     do {
128 |                         val imageId = cur.getString(cur.getColumnIndex(MediaStore.Images.Thumbnails.IMAGE_ID))
129 |                         val imagePath = cur.getString(cur.getColumnIndex(MediaStore.Images.Thumbnails.DATA))
130 |                         mThumbnailMap = mapOf(imageId to imagePath)
131 |                     } while (cur.moveToNext() && !cur.isLast)
132 |                 }
133 |             } finally {
134 |                 cur?.close()
135 |             }
136 |         }
137 | 
138 | 
139 |         /**
140 |          * 
141 |          *     @author moosphon  (about me: https://github.com/Moosphan)
142 |          *     @date   2018/09/16
143 |          *     @desc   get all videos of the phone.
144 |          * 
145 |          */
146 |         fun getLocalVideos(mContext: Context?) : List?{
147 |             val videos = ArrayList()
148 |             val resolver = mContext?.contentResolver
149 |             var cursor: Cursor? = null
150 |             try {
151 |                 cursor = resolver?.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
152 |                         arrayOf(MediaStore.Images.ImageColumns.DATA,
153 |                                 MediaStore.Video.Media._ID,
154 |                                 MediaStore.Video.Media.DISPLAY_NAME,
155 |                                 MediaStore.Video.Media.RESOLUTION,
156 |                                 MediaStore.Video.Media.SIZE,
157 |                                 MediaStore.Video.Media.DURATION,
158 |                                 MediaStore.Video.Media.DATE_MODIFIED),
159 |                         MediaStore.Video.Media.MIME_TYPE + "=?", arrayOf("video/mp4"), null)
160 |                 return if (cursor == null || !cursor.moveToFirst()) {
161 |                     null
162 |                 } else {
163 |                     while (cursor.moveToNext()){
164 |                         // video path
165 |                         val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA))
166 |                         // video id
167 |                         val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID))
168 |                         // video display name
169 |                         val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME))
170 |                         // video resolution
171 |                         val resolution = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.RESOLUTION))
172 |                         // video size
173 |                         val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE))
174 |                         // video duration
175 |                         val duration = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION))
176 |                         val date = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_MODIFIED))
177 | 
178 |                         val video = VideoMediaEntity.Builder(id.toString(), path)
179 |                                 .setTitle(name)
180 |                                 .setDateTaken(date.toString())
181 |                                 .setDuration(duration.toString())
182 |                                 .setSize(size.toString())
183 |                                 .build()
184 |                         videos.add(video)
185 |                     }
186 | 
187 |                     return videos
188 |                 }
189 |             } finally {
190 |                 if (cursor != null) {
191 |                     cursor.close()
192 |                 }
193 |             }
194 |         }
195 | 
196 |         /**
197 |          * get video thumbnail image by using [ContentResolver]
198 |          *
199 |          * @param id video id
200 |          *
201 |          * @author moosphon
202 |          */
203 |         private fun getVideoThumbnailById(context: Context?, id: Long): String?{
204 | 
205 |             //提前生成缩略图,再获取:http://stackoverflow.com/questions/27903264/how-to-get-the-video-thumbnail-path-and-not-the-bitmap
206 |             //MediaStore.Video.Thumbnails.getThumbnail(context!!.contentResolver, id, MediaStore.Video.Thumbnails.MICRO_KIND, null)
207 |             val projection = arrayOf(MediaStore.Video.Thumbnails.DATA,
208 |                     MediaStore.Video.Thumbnails.VIDEO_ID)
209 |             val thumbCursor = context?.contentResolver!!.query(
210 |                     MediaStore.Video.Thumbnails.EXTERNAL_CONTENT_URI,
211 |                     projection, MediaStore.Video.Thumbnails.VIDEO_ID
212 |                     + "=" + id, null, null)
213 |             var thumbnailUri = ""
214 |             while (thumbCursor.moveToFirst()){
215 |                 thumbnailUri = thumbCursor.getString(thumbCursor.getColumnIndex(MediaStore.Video.Thumbnails.DATA))
216 |             }
217 |             thumbCursor.close()
218 |             return thumbnailUri
219 |         }
220 | 
221 | 
222 |     }
223 | 
224 | 
225 | 
226 | }


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
 1 | 
 7 |     
12 |         
13 |             
19 |                 
22 |                 
25 |             
26 |         
27 |     
28 |     
34 | 
35 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/drawable-xxhdpi/radio_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/app/src/main/res/drawable-xxhdpi/radio_off.png


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/drawable-xxhdpi/radio_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/app/src/main/res/drawable-xxhdpi/radio_on.png


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
  1 | 
  2 | 
  7 |     
 10 |     
 15 |     
 20 |     
 25 |     
 30 |     
 35 |     
 40 |     
 45 |     
 50 |     
 55 |     
 60 |     
 65 |     
 70 |     
 75 |     
 80 |     
 85 |     
 90 |     
 95 |     
100 |     
105 |     
110 |     
115 |     
120 |     
125 |     
130 |     
135 |     
140 |     
145 |     
150 |     
155 |     
160 |     
165 |     
170 | 
171 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/drawable/image_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/app/src/main/res/drawable/image_icon.png


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/drawable/radio_selector.xml:
--------------------------------------------------------------------------------
1 | 
2 | 
3 |     
4 |     
5 |     
6 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/drawable/video_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/app/src/main/res/drawable/video_icon.png


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/layout/activity_image_select.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
10 |     
18 | 
19 |     
20 |     
24 | 
25 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
10 |     
15 |         
24 |         
31 |     
32 | 
33 |     
38 |         
47 |         
54 |     
55 | 
56 | 
57 | 
58 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/layout/activity_video_select.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
10 |     
18 | 
19 |     
20 |     
24 | 
25 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/layout/rv_item_local_video_layout.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 9 |     
14 |         
17 |             
22 | 
23 |             
33 |         
34 |     
35 | 
36 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 | 
2 | 
3 |     
4 |     
5 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 | 
2 | 
3 |     
4 |     
5 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/app/src/main/res/mipmap-hdpi/ic_launcher.png


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/app/src/main/res/mipmap-hdpi/ic_launcher_round.png


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/app/src/main/res/mipmap-mdpi/ic_launcher.png


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/app/src/main/res/mipmap-mdpi/ic_launcher_round.png


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/app/src/main/res/mipmap-xhdpi/ic_launcher.png


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/app/src/main/res/mipmap-xxhdpi/ic_launcher.png


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 | 
2 | 
3 |     #1E8AE8
4 |     #176EB9
5 |     #FF4081
6 | 
7 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 | 
2 |     LocalMediaSelector
3 | 
4 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     
10 |     
14 | 
15 | 
16 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/app/src/test/java/com/moos/media/ExampleUnitTest.java:
--------------------------------------------------------------------------------
 1 | package com.moos.media;
 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() {
15 |         assertEquals(4, 2 + 2);
16 |     }
17 | }


--------------------------------------------------------------------------------
/LocalMediaSelector/build.gradle:
--------------------------------------------------------------------------------
 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
 2 | 
 3 | buildscript {
 4 |     ext.kotlin_version = '1.2.61'
 5 | 
 6 |     repositories {
 7 |         google()
 8 |         jcenter()
 9 |     }
10 |     dependencies {
11 |         classpath 'com.android.tools.build:gradle:3.1.4'
12 |         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 | 
14 |         // NOTE: Do not place your application dependencies here; they belong
15 |         // in the individual module build.gradle files
16 |     }
17 | }
18 | 
19 | allprojects {
20 |     repositories {
21 |         google()
22 |         jcenter()
23 |     }
24 | }
25 | 
26 | task clean(type: Delete) {
27 |     delete rootProject.buildDir
28 | }
29 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/gradle.properties:
--------------------------------------------------------------------------------
 1 | # Project-wide Gradle settings.
 2 | # IDE (e.g. Android Studio) users:
 3 | # Gradle settings configured through the IDE *will override*
 4 | # any settings specified in this file.
 5 | # For more details on how to configure your build environment visit
 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
 7 | # Specifies the JVM arguments used for the daemon process.
 8 | # The setting is particularly useful for tweaking memory settings.
 9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | 
15 | android.injected.testOnly=false
16 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/gradle/wrapper/gradle-wrapper.jar


--------------------------------------------------------------------------------
/LocalMediaSelector/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Sep 19 15:55:51 CST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
7 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/gradlew:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env sh
  2 | 
  3 | ##############################################################################
  4 | ##
  5 | ##  Gradle start up script for UN*X
  6 | ##
  7 | ##############################################################################
  8 | 
  9 | # Attempt to set APP_HOME
 10 | # Resolve links: $0 may be a link
 11 | PRG="$0"
 12 | # Need this for relative symlinks.
 13 | while [ -h "$PRG" ] ; do
 14 |     ls=`ls -ld "$PRG"`
 15 |     link=`expr "$ls" : '.*-> \(.*\)$'`
 16 |     if expr "$link" : '/.*' > /dev/null; then
 17 |         PRG="$link"
 18 |     else
 19 |         PRG=`dirname "$PRG"`"/$link"
 20 |     fi
 21 | done
 22 | SAVED="`pwd`"
 23 | cd "`dirname \"$PRG\"`/" >/dev/null
 24 | APP_HOME="`pwd -P`"
 25 | cd "$SAVED" >/dev/null
 26 | 
 27 | APP_NAME="Gradle"
 28 | APP_BASE_NAME=`basename "$0"`
 29 | 
 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 31 | DEFAULT_JVM_OPTS=""
 32 | 
 33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
 34 | MAX_FD="maximum"
 35 | 
 36 | warn () {
 37 |     echo "$*"
 38 | }
 39 | 
 40 | die () {
 41 |     echo
 42 |     echo "$*"
 43 |     echo
 44 |     exit 1
 45 | }
 46 | 
 47 | # OS specific support (must be 'true' or 'false').
 48 | cygwin=false
 49 | msys=false
 50 | darwin=false
 51 | nonstop=false
 52 | case "`uname`" in
 53 |   CYGWIN* )
 54 |     cygwin=true
 55 |     ;;
 56 |   Darwin* )
 57 |     darwin=true
 58 |     ;;
 59 |   MINGW* )
 60 |     msys=true
 61 |     ;;
 62 |   NONSTOP* )
 63 |     nonstop=true
 64 |     ;;
 65 | esac
 66 | 
 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 68 | 
 69 | # Determine the Java command to use to start the JVM.
 70 | if [ -n "$JAVA_HOME" ] ; then
 71 |     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
 72 |         # IBM's JDK on AIX uses strange locations for the executables
 73 |         JAVACMD="$JAVA_HOME/jre/sh/java"
 74 |     else
 75 |         JAVACMD="$JAVA_HOME/bin/java"
 76 |     fi
 77 |     if [ ! -x "$JAVACMD" ] ; then
 78 |         die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
 79 | 
 80 | Please set the JAVA_HOME variable in your environment to match the
 81 | location of your Java installation."
 82 |     fi
 83 | else
 84 |     JAVACMD="java"
 85 |     which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 86 | 
 87 | Please set the JAVA_HOME variable in your environment to match the
 88 | location of your Java installation."
 89 | fi
 90 | 
 91 | # Increase the maximum file descriptors if we can.
 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
 93 |     MAX_FD_LIMIT=`ulimit -H -n`
 94 |     if [ $? -eq 0 ] ; then
 95 |         if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
 96 |             MAX_FD="$MAX_FD_LIMIT"
 97 |         fi
 98 |         ulimit -n $MAX_FD
 99 |         if [ $? -ne 0 ] ; then
100 |             warn "Could not set maximum file descriptor limit: $MAX_FD"
101 |         fi
102 |     else
103 |         warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 |     fi
105 | fi
106 | 
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 |     GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 | 
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 |     APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 |     CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 |     JAVACMD=`cygpath --unix "$JAVACMD"`
117 | 
118 |     # We build the pattern for arguments to be converted via cygpath
119 |     ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 |     SEP=""
121 |     for dir in $ROOTDIRSRAW ; do
122 |         ROOTDIRS="$ROOTDIRS$SEP$dir"
123 |         SEP="|"
124 |     done
125 |     OURCYGPATTERN="(^($ROOTDIRS))"
126 |     # Add a user-defined pattern to the cygpath arguments
127 |     if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 |         OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 |     fi
130 |     # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 |     i=0
132 |     for arg in "$@" ; do
133 |         CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 |         CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
135 | 
136 |         if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
137 |             eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 |         else
139 |             eval `echo args$i`="\"$arg\""
140 |         fi
141 |         i=$((i+1))
142 |     done
143 |     case $i in
144 |         (0) set -- ;;
145 |         (1) set -- "$args0" ;;
146 |         (2) set -- "$args0" "$args1" ;;
147 |         (3) set -- "$args0" "$args1" "$args2" ;;
148 |         (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 |         (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 |         (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 |         (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 |         (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 |         (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 |     esac
155 | fi
156 | 
157 | # Escape application args
158 | save () {
159 |     for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 |     echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 | 
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 | 
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 |   cd "$(dirname "$0")"
170 | fi
171 | 
172 | exec "$JAVACMD" "$@"
173 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/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 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 | 
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
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 Windows variants
50 | 
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | 
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 | 
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 | 
61 | set CMD_LINE_ARGS=%*
62 | 
63 | :execute
64 | @rem Setup the command line
65 | 
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 | 
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 | 
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 | 
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 | 
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 | 
84 | :omega
85 | 


--------------------------------------------------------------------------------
/LocalMediaSelector/image/art_customize_ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/image/art_customize_ui.png


--------------------------------------------------------------------------------
/LocalMediaSelector/image/art_select_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/image/art_select_image.png


--------------------------------------------------------------------------------
/LocalMediaSelector/image/art_select_video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moosphan/LocalVideoImage-selector/309b0bb2517e4e2d8440cadedaf32c071e7ebbf2/LocalMediaSelector/image/art_select_video.png


--------------------------------------------------------------------------------
/LocalMediaSelector/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | # Eyebrows-MediaSelector
 2 | Provide a way to show local videos and images in the phone, also support single-selection  and multi-selection.
 3 | 
 4 | 实现一款简单易用的本地图片和视频选择器示例,支持单选和多选功能。
 5 | 
 6 | 本地图片和视频数据的获取和封装,可以完全自由定制化自己的UI,后续将继续优化加载速度和选择状态,先简单看一下目前的效果图:
 7 | 
 8 | ![](https://github.com/Moosphan/LocalVideoImage-selector/blob/3cbf292479abbfb79912e252b4f68152a61dca59/LocalMediaSelector/image/art_select_image.png)     ![](https://github.com/Moosphan/LocalVideoImage-selector/blob/3cbf292479abbfb79912e252b4f68152a61dca59/LocalMediaSelector/image/art_select_video.png)
 9 | 
10 | 
11 | 
12 | 定制化UI简单示例:
13 | 
14 | ![](https://github.com/Moosphan/LocalVideoImage-selector/blob/3cbf292479abbfb79912e252b4f68152a61dca59/LocalMediaSelector/image/art_customize_ui.png)
15 | 
16 | 
17 | 
18 | #### 关于我
19 | 
20 | > QQ群:601924443
21 | 
22 | 
23 | 
24 | #### License
25 | 
26 | ```
27 | MIT License
28 | 
29 | Copyright (c) 2018 Moosphon
30 | 
31 | Permission is hereby granted, free of charge, to any person obtaining a copy
32 | of this software and associated documentation files (the "Software"), to deal
33 | in the Software without restriction, including without limitation the rights
34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
35 | copies of the Software, and to permit persons to whom the Software is
36 | furnished to do so, subject to the following conditions:
37 | 
38 | The above copyright notice and this permission notice shall be included in all
39 | copies or substantial portions of the Software.
40 | 
41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
47 | SOFTWARE.
48 | ```
49 | 
50 | 


--------------------------------------------------------------------------------