├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── steven │ │ └── selectimage │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── steven │ │ │ └── selectimage │ │ │ ├── MyAppGlideModule.java │ │ │ ├── model │ │ │ ├── Image.java │ │ │ └── ImageFolder.java │ │ │ ├── ui │ │ │ ├── BaseActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── PreviewImageActivity.java │ │ │ ├── SelectImageActivity.java │ │ │ └── adapter │ │ │ │ ├── ImageAdapter.java │ │ │ │ ├── ImageFolderAdapter.java │ │ │ │ └── SelectedImageAdapter.java │ │ │ ├── utils │ │ │ ├── BitmapUtil.java │ │ │ ├── StatusBarUtil.java │ │ │ └── TDevice.java │ │ │ └── widget │ │ │ ├── ImageFolderView.java │ │ │ ├── PreViewImageView.java │ │ │ ├── SquareFrameLayout.java │ │ │ ├── SquareImageView.java │ │ │ └── recyclerview │ │ │ ├── CommonRecycleAdapter.java │ │ │ ├── CommonViewHolder.java │ │ │ ├── MultiTypeSupport.java │ │ │ ├── OnItemClickListener.java │ │ │ └── SpaceGridItemDecoration.java │ └── res │ │ ├── anim │ │ ├── popup_select_image_hide.xml │ │ └── popup_select_image_show.xml │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_check_background.xml │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_preview_image.xml │ │ ├── activity_select_image.xml │ │ ├── image_folder_layout.xml │ │ ├── item_list_camera.xml │ │ ├── item_list_folder.xml │ │ ├── item_list_image.xml │ │ ├── selected_image_item.xml │ │ └── test.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_back.png │ │ ├── ic_camera.png │ │ ├── ic_chb_normal.png │ │ ├── ic_chb_selectd.png │ │ ├── ic_default_image.png │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── file_paths.xml │ └── test │ └── java │ └── com │ └── steven │ └── selectimage │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.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/StevenYan88/SelectImage/dd6ad3d1c83a88327b391dac8eb9dd995747cc3c/.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/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 35 | 36 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 87 | 99 | 100 | 101 | ApexVCS 102 | 103 | 104 | 105 | 106 | 107 | 109 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### Android--选择多张图片,支持拖拽删除、排序、预览图片 2 | 3 | **效果图** 4 | ![2019-03-28_11_45_57.gif](https://upload-images.jianshu.io/upload_images/1472453-e0153839f1a9a8b9.gif?imageMogr2/auto-orient/strip) 5 | 6 | 截图1 | 截图2 | 截图 3 | 截图 4 7 | ---|---|---|--- 8 | | ![异步加载图片](https://upload-images.jianshu.io/upload_images/1472453-9e417e5f60ebdaa5.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)| ![图片文件夹](https://upload-images.jianshu.io/upload_images/1472453-7592bab5f2c2c0a8.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)|![选择图片](https://upload-images.jianshu.io/upload_images/1472453-6fea963cf69a4560.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)|![已选择的图片](https://upload-images.jianshu.io/upload_images/1472453-f7708e64f82e4c62.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 9 | 10 | 11 | ##### 具体思路(知识点): 12 | 1. 异步加载相册图片; 13 | 2. 自定义相册文件夹; 14 | 3. 支持单选、多选(最多9张)图片; 15 | 4. ItemTouchHelper实现拖拽、排序、删除; 16 | 5. 对Canvas画布操作、变换,结合属性动画,实现图片的放大、缩小等; 17 | 18 | **详细看代码怎么实现的!!!** 19 | 20 | **License** 21 | 22 | Copyright 2019 StevenYan88 23 | 24 | Licensed under the Apache License, Version 2.0 (the "License"); 25 | you may not use this file except in compliance with the License. 26 | You may obtain a copy of the License at 27 | 28 | http://www.apache.org/licenses/LICENSE-2.0 29 | 30 | Unless required by applicable law or agreed to in writing, software 31 | distributed under the License is distributed on an "AS IS" BASIS, 32 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | See the License for the specific language governing permissions and 34 | limitations under the License. 35 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId "com.steven.selectimage" 7 | minSdkVersion 21 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | compileOptions { 20 | sourceCompatibility JavaVersion.VERSION_1_8 21 | targetCompatibility JavaVersion.VERSION_1_8 22 | 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(include: ['*.jar'], dir: 'libs') 28 | implementation 'com.android.support:appcompat-v7:27.1.1' 29 | implementation 'com.android.support:design:27.1.1' 30 | implementation 'com.android.support.constraint:constraint-layout:1.1.2' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 34 | implementation 'com.android.support:recyclerview-v7:27.1.1' 35 | implementation 'com.github.bumptech.glide:glide:4.6.1' 36 | annotationProcessor 'com.github.bumptech.glide:compiler:4.6.1' 37 | implementation 'com.jakewharton:butterknife:8.8.1' 38 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' 39 | } 40 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/steven/selectimage/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage; 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.steven.selectimage", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 36 | 37 | 38 | 39 | 44 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/MyAppGlideModule.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage; 2 | 3 | import com.bumptech.glide.annotation.GlideModule; 4 | import com.bumptech.glide.module.AppGlideModule; 5 | 6 | /** 7 | * Description: 8 | * Data:7/5/2018-3:43 PM 9 | * 10 | * @author yanzhiwen 11 | */ 12 | @GlideModule 13 | public class MyAppGlideModule extends AppGlideModule { 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/model/Image.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.model; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | /** 7 | * Description: 8 | * Data:9/4/2018-11:50 AM 9 | * 10 | * @author yanzhiwen 11 | */ 12 | public class Image implements Parcelable { 13 | 14 | private int id; 15 | private String path; 16 | private String thumbPath; 17 | private boolean isSelect; 18 | private String folderName; 19 | private String name; 20 | private long date; 21 | 22 | public int getId() { 23 | return id; 24 | } 25 | 26 | public void setId(int id) { 27 | this.id = id; 28 | } 29 | 30 | public String getPath() { 31 | return path; 32 | } 33 | 34 | public void setPath(String path) { 35 | this.path = path; 36 | } 37 | 38 | public String getThumbPath() { 39 | return thumbPath; 40 | } 41 | 42 | public void setThumbPath(String thumbPath) { 43 | this.thumbPath = thumbPath; 44 | } 45 | 46 | public boolean isSelect() { 47 | return isSelect; 48 | } 49 | 50 | public void setSelect(boolean select) { 51 | isSelect = select; 52 | } 53 | 54 | public String getFolderName() { 55 | return folderName; 56 | } 57 | 58 | public void setFolderName(String folderName) { 59 | this.folderName = folderName; 60 | } 61 | 62 | public String getName() { 63 | return name; 64 | } 65 | 66 | public void setName(String name) { 67 | this.name = name; 68 | } 69 | 70 | public long getDate() { 71 | return date; 72 | } 73 | 74 | public void setDate(long date) { 75 | this.date = date; 76 | } 77 | 78 | @Override 79 | public boolean equals(Object o) { 80 | if (o instanceof Image) { 81 | return this.path.equals(((Image) o).getPath()); 82 | } 83 | return false; 84 | } 85 | 86 | @Override 87 | public int describeContents() { 88 | return 0; 89 | } 90 | 91 | @Override 92 | public void writeToParcel(Parcel dest, int flags) { 93 | dest.writeInt(this.id); 94 | dest.writeString(this.path); 95 | dest.writeString(this.thumbPath); 96 | dest.writeByte(this.isSelect ? (byte) 1 : (byte) 0); 97 | dest.writeString(this.folderName); 98 | dest.writeString(this.name); 99 | dest.writeLong(this.date); 100 | } 101 | 102 | public Image() { 103 | } 104 | 105 | protected Image(Parcel in) { 106 | this.id = in.readInt(); 107 | this.path = in.readString(); 108 | this.thumbPath = in.readString(); 109 | this.isSelect = in.readByte() != 0; 110 | this.folderName = in.readString(); 111 | this.name = in.readString(); 112 | this.date = in.readLong(); 113 | } 114 | 115 | public static final Creator CREATOR = new Creator() { 116 | @Override 117 | public Image createFromParcel(Parcel source) { 118 | return new Image(source); 119 | } 120 | 121 | @Override 122 | public Image[] newArray(int size) { 123 | return new Image[size]; 124 | } 125 | }; 126 | } 127 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/model/ImageFolder.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.model; 2 | 3 | 4 | import java.util.ArrayList; 5 | 6 | /** 7 | * Description: 8 | * Data:9/4/2018-11:49 AM 9 | * 10 | * @author yanzhiwen 11 | */ 12 | public class ImageFolder { 13 | private String name; 14 | private String path; 15 | private String albumPath; 16 | private ArrayList images = new ArrayList<>(); 17 | 18 | public String getName() { 19 | return name; 20 | } 21 | 22 | public void setName(String name) { 23 | this.name = name; 24 | } 25 | 26 | public String getPath() { 27 | return path; 28 | } 29 | 30 | public void setPath(String path) { 31 | this.path = path; 32 | } 33 | 34 | public ArrayList getImages() { 35 | return images; 36 | } 37 | 38 | public String getAlbumPath() { 39 | return albumPath; 40 | } 41 | 42 | public void setAlbumPath(String albumPath) { 43 | this.albumPath = albumPath; 44 | } 45 | 46 | @Override 47 | public boolean equals(Object o) { 48 | if (o != null && o instanceof ImageFolder) { 49 | if ((( ImageFolder ) o).getPath() == null && path != null) 50 | return false; 51 | String oPath = (( ImageFolder ) o).getPath().toLowerCase(); 52 | return oPath.equals(this.path.toLowerCase()); 53 | } 54 | return false; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/ui/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.ui; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | import butterknife.ButterKnife; 8 | import butterknife.Unbinder; 9 | 10 | /** 11 | * Description: 12 | * Data:9/5/2018-10:34 AM 13 | * 14 | * @author yanzhiwen 15 | */ 16 | public abstract class BaseActivity extends AppCompatActivity { 17 | private Unbinder mUnbinder; 18 | 19 | @Override 20 | protected void onCreate(@Nullable Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(getLayoutId()); 23 | mUnbinder = ButterKnife.bind(this); 24 | init(); 25 | } 26 | 27 | protected abstract int getLayoutId(); 28 | 29 | protected abstract void init(); 30 | 31 | @Override 32 | protected void onDestroy() { 33 | super.onDestroy(); 34 | if (mUnbinder != null) { 35 | mUnbinder.unbind(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/ui/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.ui; 2 | 3 | import android.Manifest; 4 | import android.content.Intent; 5 | import android.content.pm.PackageManager; 6 | import android.support.annotation.NonNull; 7 | import android.support.v4.app.ActivityCompat; 8 | import android.support.v4.content.ContextCompat; 9 | import android.support.v7.widget.GridLayoutManager; 10 | import android.support.v7.widget.LinearLayoutManager; 11 | import android.support.v7.widget.RecyclerView; 12 | import android.support.v7.widget.helper.ItemTouchHelper; 13 | import android.view.View; 14 | import android.widget.TextView; 15 | import android.widget.Toast; 16 | 17 | import com.steven.selectimage.R; 18 | import com.steven.selectimage.model.Image; 19 | import com.steven.selectimage.ui.adapter.SelectedImageAdapter; 20 | import com.steven.selectimage.utils.TDevice; 21 | import com.steven.selectimage.widget.recyclerview.SpaceGridItemDecoration; 22 | 23 | import java.util.ArrayList; 24 | import java.util.Collections; 25 | 26 | import butterknife.BindView; 27 | 28 | public class MainActivity extends BaseActivity { 29 | private static final int PERMISSION_REQUEST_CODE = 0; 30 | private static final int SELECT_IMAGE_REQUEST = 0x0011; 31 | @BindView(R.id.rv_selected_image) 32 | RecyclerView mSelectedImageRv; 33 | @BindView(R.id.drag_tip) 34 | TextView mDragTip; 35 | private ArrayList mSelectImages = new ArrayList<>(); 36 | private SelectedImageAdapter mAdapter; 37 | 38 | @Override 39 | protected int getLayoutId() { 40 | return R.layout.activity_main; 41 | } 42 | 43 | @Override 44 | protected void init() { 45 | mSelectedImageRv.setLayoutManager(new GridLayoutManager(this, 3, LinearLayoutManager.VERTICAL, false)); 46 | mSelectedImageRv.addItemDecoration(new SpaceGridItemDecoration((int) TDevice.dipToPx(getResources(), 1))); 47 | findViewById(R.id.btn_select).setOnClickListener(v -> selectImage()); 48 | } 49 | 50 | private void selectImage() { 51 | int isPermission1 = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE); 52 | int isPermission2 = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); 53 | if (isPermission1 == PackageManager.PERMISSION_GRANTED && isPermission2 == PackageManager.PERMISSION_GRANTED) { 54 | startActivity(); 55 | } else { 56 | //申请权限 57 | ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, 58 | Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); 59 | } 60 | } 61 | 62 | private void startActivity() { 63 | Intent intent = new Intent(this, SelectImageActivity.class); 64 | intent.putParcelableArrayListExtra("selected_images", mSelectImages); 65 | startActivityForResult(intent, SELECT_IMAGE_REQUEST); 66 | } 67 | 68 | @Override 69 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 70 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 71 | if (requestCode == PERMISSION_REQUEST_CODE) { 72 | if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 73 | startActivity(); 74 | } else { 75 | //申请权限 76 | ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, 77 | Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); 78 | Toast.makeText(this, "需要您的存储权限!", Toast.LENGTH_SHORT).show(); 79 | } 80 | } 81 | } 82 | 83 | @Override 84 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 85 | super.onActivityResult(requestCode, resultCode, data); 86 | if (resultCode == RESULT_OK) { 87 | if (requestCode == SELECT_IMAGE_REQUEST && data != null) { 88 | ArrayList selectImages = data.getParcelableArrayListExtra(SelectImageActivity.EXTRA_RESULT); 89 | mSelectImages.clear(); 90 | mSelectImages.addAll(selectImages); 91 | if (mSelectImages.size() > 1) { 92 | mDragTip.setVisibility(View.VISIBLE); 93 | } 94 | mAdapter = new SelectedImageAdapter(this, mSelectImages, R.layout.selected_image_item); 95 | mSelectedImageRv.setAdapter(mAdapter); 96 | mItemTouchHelper.attachToRecyclerView(mSelectedImageRv); 97 | 98 | } 99 | } 100 | } 101 | 102 | private ItemTouchHelper mItemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() { 103 | @Override 104 | public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { 105 | 106 | // 获取触摸响应的方向 包含两个 1.拖动dragFlags 2.侧滑删除swipeFlags 107 | // 代表只能是向左侧滑删除,当前可以是这样ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT 108 | int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; 109 | int dragFlags; 110 | if (recyclerView.getLayoutManager() instanceof GridLayoutManager) { 111 | dragFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT | ItemTouchHelper.UP | ItemTouchHelper.DOWN; 112 | } else { 113 | dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; 114 | } 115 | return makeMovementFlags(dragFlags, swipeFlags); 116 | } 117 | 118 | /** 119 | * 拖动的时候不断的回调方法 120 | */ 121 | @Override 122 | public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { 123 | //获取到原来的位置 124 | int fromPosition = viewHolder.getAdapterPosition(); 125 | //获取到拖到的位置 126 | int targetPosition = target.getAdapterPosition(); 127 | if (fromPosition < targetPosition) { 128 | for (int i = fromPosition; i < targetPosition; i++) { 129 | Collections.swap(mSelectImages, i, i + 1); 130 | } 131 | } else { 132 | for (int i = fromPosition; i > targetPosition; i--) { 133 | Collections.swap(mSelectImages, i, i - 1); 134 | } 135 | } 136 | mAdapter.notifyItemMoved(fromPosition, targetPosition); 137 | return true; 138 | } 139 | 140 | /** 141 | * 侧滑删除后会回调的方法 142 | */ 143 | @Override 144 | public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { 145 | int position = viewHolder.getAdapterPosition(); 146 | mSelectImages.remove(position); 147 | mAdapter.notifyItemRemoved(position); 148 | } 149 | }); 150 | } 151 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/ui/PreviewImageActivity.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.ui; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.v4.view.PagerAdapter; 5 | import android.support.v4.view.ViewPager; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.TextView; 9 | 10 | import com.steven.selectimage.GlideApp; 11 | import com.steven.selectimage.R; 12 | import com.steven.selectimage.model.Image; 13 | import com.steven.selectimage.widget.PreViewImageView; 14 | 15 | import java.util.List; 16 | 17 | import butterknife.BindView; 18 | 19 | public class PreviewImageActivity extends BaseActivity implements ViewPager.OnPageChangeListener { 20 | @BindView(R.id.viewPager) 21 | ViewPager mViewPager; 22 | @BindView(R.id.tv_count) 23 | TextView mCount; 24 | private List mSelectedImages; 25 | 26 | @Override 27 | protected int getLayoutId() { 28 | return R.layout.activity_preview_image; 29 | } 30 | 31 | @Override 32 | protected void init() { 33 | mSelectedImages = getIntent().getParcelableArrayListExtra("preview_images"); 34 | mViewPager.setAdapter(mViewPagerAdapter); 35 | mViewPager.addOnPageChangeListener(this); 36 | mCount.setText(String.format("%s/%s", 1, mSelectedImages.size())); 37 | 38 | } 39 | 40 | private PagerAdapter mViewPagerAdapter = new PagerAdapter() { 41 | @Override 42 | public int getCount() { 43 | return mSelectedImages.size(); 44 | } 45 | 46 | @Override 47 | public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { 48 | return view == object; 49 | } 50 | 51 | @NonNull 52 | @Override 53 | public Object instantiateItem(@NonNull ViewGroup container, int position) { 54 | PreViewImageView imageView = new PreViewImageView(container.getContext()); 55 | GlideApp.with(imageView.getContext()) 56 | .load(mSelectedImages.get(position).getPath()) 57 | .centerCrop() 58 | .into(imageView); 59 | container.addView(imageView); 60 | return imageView; 61 | } 62 | 63 | @Override 64 | public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { 65 | container.removeView((View) object); 66 | } 67 | }; 68 | 69 | 70 | @Override 71 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 72 | 73 | } 74 | 75 | @Override 76 | public void onPageSelected(int position) { 77 | mCount.setText(String.format("%s/%s", (position + 1), mSelectedImages.size())); 78 | 79 | } 80 | 81 | @Override 82 | public void onPageScrollStateChanged(int state) { 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/ui/SelectImageActivity.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.ui; 2 | 3 | 4 | import android.Manifest; 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager; 7 | import android.database.Cursor; 8 | import android.graphics.Bitmap; 9 | import android.graphics.BitmapFactory; 10 | import android.net.Uri; 11 | import android.os.Build; 12 | import android.os.Bundle; 13 | import android.os.Environment; 14 | import android.os.Parcelable; 15 | import android.provider.MediaStore; 16 | import android.support.annotation.NonNull; 17 | import android.support.annotation.Nullable; 18 | import android.support.v4.app.ActivityCompat; 19 | import android.support.v4.app.LoaderManager; 20 | import android.support.v4.content.ContextCompat; 21 | import android.support.v4.content.CursorLoader; 22 | import android.support.v4.content.FileProvider; 23 | import android.support.v4.content.Loader; 24 | import android.support.v7.widget.GridLayoutManager; 25 | import android.support.v7.widget.LinearLayoutManager; 26 | import android.support.v7.widget.RecyclerView; 27 | import android.text.TextUtils; 28 | import android.util.Log; 29 | import android.view.View; 30 | import android.widget.TextView; 31 | import android.widget.Toast; 32 | 33 | import com.steven.selectimage.R; 34 | import com.steven.selectimage.model.Image; 35 | import com.steven.selectimage.model.ImageFolder; 36 | import com.steven.selectimage.ui.adapter.ImageAdapter; 37 | import com.steven.selectimage.ui.adapter.ImageFolderAdapter; 38 | import com.steven.selectimage.utils.StatusBarUtil; 39 | import com.steven.selectimage.utils.TDevice; 40 | import com.steven.selectimage.widget.ImageFolderView; 41 | import com.steven.selectimage.widget.recyclerview.MultiTypeSupport; 42 | import com.steven.selectimage.widget.recyclerview.SpaceGridItemDecoration; 43 | 44 | import java.io.File; 45 | import java.io.FileNotFoundException; 46 | import java.io.IOException; 47 | import java.text.SimpleDateFormat; 48 | import java.util.ArrayList; 49 | import java.util.Date; 50 | import java.util.List; 51 | import java.util.Locale; 52 | 53 | import butterknife.BindView; 54 | import butterknife.OnClick; 55 | 56 | public class SelectImageActivity extends BaseActivity implements ImageFolderView.ImageFolderViewListener, ImageAdapter.onCameraClickListener { 57 | // 返回选择图片列表的EXTRA_KEY 58 | public static final String EXTRA_RESULT = "EXTRA_RESULT"; 59 | public static final int MAX_SIZE = 9; 60 | private static final int PERMISSION_REQUEST_CODE = 88; 61 | private static final int TAKE_PHOTO = 99; 62 | @BindView(R.id.tv_back) 63 | TextView mTvBack; 64 | @BindView(R.id.tv_ok) 65 | TextView mTvSelectCount; 66 | @BindView(R.id.rv) 67 | RecyclerView mRvImage; 68 | @BindView(R.id.tv_photo) 69 | TextView mTvPhoto; 70 | @BindView(R.id.tv_preview) 71 | TextView mTvPreview; 72 | @BindView(R.id.image_folder_view) 73 | ImageFolderView mImageFolderView; 74 | private boolean mHasCamera = true; 75 | //被选中图片的集合 76 | private List mSelectedImages = new ArrayList<>(); 77 | private List mImages = new ArrayList<>(); 78 | private List mImageFolders = new ArrayList<>(); 79 | private ImageAdapter mImageAdapter; 80 | private ImageFolderAdapter mImageFolderAdapter; 81 | private Uri mImageUri; 82 | private File takePhotoImageFile; 83 | 84 | 85 | @Override 86 | protected int getLayoutId() { 87 | return R.layout.activity_select_image; 88 | } 89 | 90 | @Override 91 | protected void init() { 92 | //设置状态栏的颜色 93 | StatusBarUtil.statusBarTintColor(this, ContextCompat.getColor(this, R.color.color_black)); 94 | setupSelectedImages(); 95 | mRvImage.setLayoutManager(new GridLayoutManager(this, 4, LinearLayoutManager.VERTICAL, false)); 96 | mRvImage.addItemDecoration(new SpaceGridItemDecoration((int) TDevice.dipToPx(getResources(), 1))); 97 | //异步加载图片 98 | getSupportLoaderManager().initLoader(0, null, mLoaderCallbacks); 99 | mImageFolderView.setListener(this); 100 | 101 | } 102 | 103 | private void setupSelectedImages() { 104 | Intent intent = getIntent(); 105 | ArrayList selectImages = intent.getParcelableArrayListExtra("selected_images"); 106 | mSelectedImages.addAll(selectImages); 107 | 108 | if (mSelectedImages.size() > 0 && mSelectedImages.size() <= MAX_SIZE) { 109 | mTvPreview.setClickable(true); 110 | mTvPreview.setText(String.format("预览(%d/9) ", mSelectedImages.size())); 111 | mTvPreview.setTextColor(ContextCompat.getColor(SelectImageActivity.this, R.color.colorAccent)); 112 | } 113 | } 114 | 115 | 116 | @OnClick({R.id.tv_back, R.id.tv_ok, R.id.tv_photo, R.id.tv_preview}) 117 | public void onViewClicked(View view) { 118 | switch (view.getId()) { 119 | case R.id.tv_back: 120 | finish(); 121 | break; 122 | case R.id.tv_ok: 123 | Intent intent = new Intent(); 124 | intent.putParcelableArrayListExtra(EXTRA_RESULT, (ArrayList) mSelectedImages); 125 | setResult(RESULT_OK, intent); 126 | finish(); 127 | break; 128 | case R.id.tv_photo: 129 | if (mImageFolderView.isShowing()) { 130 | mImageFolderView.hide(); 131 | } else { 132 | mImageFolderView.show(); 133 | } 134 | break; 135 | case R.id.tv_preview: 136 | Intent previewIntent = new Intent(this, PreviewImageActivity.class); 137 | previewIntent.putParcelableArrayListExtra("preview_images", (ArrayList) mSelectedImages); 138 | startActivity(previewIntent); 139 | break; 140 | } 141 | } 142 | 143 | 144 | private void addImageFoldersToAdapter() { 145 | if (mImageFolderAdapter == null) { 146 | mImageFolderAdapter = new ImageFolderAdapter(this, mImageFolders, R.layout.item_list_folder); 147 | mImageFolderView.setAdapter(mImageFolderAdapter); 148 | } else { 149 | mImageFolderAdapter.notifyDataSetChanged(); 150 | } 151 | } 152 | 153 | private void addImagesToAdapter(ArrayList images) { 154 | mImages.clear(); 155 | mImages.addAll(images); 156 | if (mImageAdapter == null) { 157 | mImageAdapter = new ImageAdapter(this, mImages, mSelectedImages, mMultiTypeSupport); 158 | mRvImage.setAdapter(mImageAdapter); 159 | } else { 160 | mImageAdapter.notifyDataSetChanged(); 161 | } 162 | mImageAdapter.setSelectImageCountListener(mOnSelectImageCountListener); 163 | mImageAdapter.setOnCameraClickListener(this); 164 | } 165 | 166 | private MultiTypeSupport mMultiTypeSupport = image -> { 167 | if (TextUtils.isEmpty(image.getPath())) { 168 | return R.layout.item_list_camera; 169 | } 170 | return R.layout.item_list_image; 171 | }; 172 | /*************************************已选择的图片回调的方法************************************************/ 173 | 174 | private ImageAdapter.onSelectImageCountListener mOnSelectImageCountListener = new ImageAdapter.onSelectImageCountListener() { 175 | @Override 176 | public void onSelectImageCount(int count) { 177 | if (count == 0) { 178 | mTvPreview.setClickable(false); 179 | mTvPreview.setText("预览"); 180 | mTvPreview.setTextColor(ContextCompat.getColor(SelectImageActivity.this, R.color.colorAccentGray)); 181 | } else if (count > 0 && count <= MAX_SIZE) { 182 | mTvPreview.setClickable(true); 183 | mTvPreview.setText(String.format("预览(%d/9) ", count)); 184 | mTvPreview.setTextColor(ContextCompat.getColor(SelectImageActivity.this, R.color.colorAccent)); 185 | } 186 | } 187 | 188 | @Override 189 | public void onSelectImageList(List images) { 190 | mSelectedImages = images; 191 | } 192 | }; 193 | 194 | /*************************************异步加载相册图片************************************************/ 195 | 196 | private LoaderManager.LoaderCallbacks mLoaderCallbacks = new LoaderManager.LoaderCallbacks() { 197 | private final String[] IMAGE_PROJECTION = { 198 | MediaStore.Images.Media.DATA, 199 | MediaStore.Images.Media.DISPLAY_NAME, 200 | MediaStore.Images.Media.DATE_ADDED, 201 | MediaStore.Images.Media._ID, 202 | MediaStore.Images.Media.MINI_THUMB_MAGIC, 203 | MediaStore.Images.Media.BUCKET_DISPLAY_NAME}; 204 | 205 | //创建一个CursorLoader,去异步加载相册的图片 206 | @NonNull 207 | @Override 208 | public Loader onCreateLoader(int id, @Nullable Bundle args) { 209 | return new CursorLoader(SelectImageActivity.this, 210 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION, 211 | null, null, IMAGE_PROJECTION[2] + " DESC"); 212 | } 213 | 214 | @Override 215 | public void onLoadFinished(@NonNull Loader loader, Cursor data) { 216 | if (data != null) { 217 | ArrayList images = new ArrayList<>(); 218 | //是否显示照相图片 219 | if (mHasCamera) { 220 | //添加到第一个的位置(默认) 221 | images.add(new Image()); 222 | } 223 | ImageFolder defaultFolder = new ImageFolder(); 224 | defaultFolder.setName("全部照片"); 225 | defaultFolder.setPath(""); 226 | mImageFolders.add(defaultFolder); 227 | 228 | int count = data.getCount(); 229 | if (count > 0) { 230 | data.moveToFirst(); 231 | do { 232 | String path = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[0])); 233 | String name = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[1])); 234 | long dateTime = data.getLong(data.getColumnIndexOrThrow(IMAGE_PROJECTION[2])); 235 | int id = data.getInt(data.getColumnIndexOrThrow(IMAGE_PROJECTION[3])); 236 | String thumbPath = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[4])); 237 | String bucket = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[5])); 238 | 239 | Image image = new Image(); 240 | image.setPath(path); 241 | image.setName(name); 242 | image.setDate(dateTime); 243 | image.setId(id); 244 | image.setThumbPath(thumbPath); 245 | image.setFolderName(bucket); 246 | images.add(image); 247 | //如果是被选中的图片 248 | if (mSelectedImages.size() > 0) { 249 | for (Image i : mSelectedImages) { 250 | if (i.getPath().equals(image.getPath())) { 251 | image.setSelect(true); 252 | } 253 | } 254 | } 255 | //设置图片分类的文件夹 256 | File imageFile = new File(path); 257 | File folderFile = imageFile.getParentFile(); 258 | ImageFolder folder = new ImageFolder(); 259 | folder.setName(folderFile.getName()); 260 | folder.setPath(folderFile.getAbsolutePath()); 261 | //ImageFolder复写了equal方法,equal方法比较的是文件夹的路径 262 | if (!mImageFolders.contains(folder)) { 263 | folder.getImages().add(image); 264 | //默认相册封面 265 | folder.setAlbumPath(image.getPath()); 266 | mImageFolders.add(folder); 267 | } else { 268 | ImageFolder imageFolder = mImageFolders.get(mImageFolders.indexOf(folder)); 269 | imageFolder.getImages().add(image); 270 | } 271 | } while (data.moveToNext()); 272 | } 273 | addImagesToAdapter(images); 274 | //全部照片 275 | defaultFolder.getImages().addAll(images); 276 | if (mHasCamera) { 277 | defaultFolder.setAlbumPath(images.size() > 1 ? images.get(1).getPath() : null); 278 | } else { 279 | defaultFolder.setAlbumPath(images.size() > 0 ? images.get(0).getPath() : null); 280 | } 281 | //删除掉不存在的,在于用户选择了相片,又去相册删除 282 | if (mSelectedImages.size() > 0) { 283 | List rs = new ArrayList<>(); 284 | for (Image i : mSelectedImages) { 285 | File f = new File(i.getPath()); 286 | if (!f.exists()) { 287 | rs.add(i); 288 | } 289 | } 290 | mSelectedImages.removeAll(rs); 291 | } 292 | } 293 | mImageFolderView.setImageFolders(mImageFolders); 294 | addImageFoldersToAdapter(); 295 | } 296 | 297 | @Override 298 | public void onLoaderReset(@NonNull Loader loader) { 299 | 300 | } 301 | }; 302 | 303 | 304 | @Override 305 | public void onSelectFolder(ImageFolderView imageFolderView, ImageFolder imageFolder) { 306 | addImagesToAdapter(imageFolder.getImages()); 307 | mRvImage.scrollToPosition(0); 308 | mTvPhoto.setText(imageFolder.getName()); 309 | } 310 | 311 | @Override 312 | public void onDismiss() { 313 | 314 | } 315 | 316 | @Override 317 | public void onShow() { 318 | 319 | } 320 | 321 | /*************************************相机拍照************************************************/ 322 | 323 | @Override 324 | public void onCameraClick() { 325 | //首先申请下相机权限 326 | int isPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA); 327 | if (isPermission == PackageManager.PERMISSION_GRANTED) { 328 | takePhoto(); 329 | } else { 330 | //申请权限 331 | ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CODE); 332 | } 333 | } 334 | 335 | @Override 336 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 337 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 338 | if (requestCode == PERMISSION_REQUEST_CODE) { 339 | if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 340 | takePhoto(); 341 | } else { 342 | ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CODE); 343 | Toast.makeText(this, "需要您的相机权限!", Toast.LENGTH_SHORT).show(); 344 | } 345 | } 346 | } 347 | 348 | 349 | private void takePhoto() { 350 | //用来打开相机的Intent 351 | Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 352 | //这句作用是如果没有相机则该应用不会闪退,要是不加这句则当系统没有相机应用的时候该应用会闪退 353 | if (takePhotoIntent.resolveActivity(getPackageManager()) != null) { 354 | takePhotoImageFile = createImageFile(); 355 | if (takePhotoImageFile != null) { 356 | Log.i("take photo", takePhotoImageFile.getAbsolutePath()); 357 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 358 | ///7.0以上要通过FileProvider将File转化为Uri 359 | mImageUri = FileProvider.getUriForFile(this, this.getPackageName() + ".fileprovider", takePhotoImageFile); 360 | } else { 361 | //7.0以下则直接使用Uri的fromFile方法将File转化为Uri 362 | mImageUri = Uri.fromFile(takePhotoImageFile); 363 | } 364 | //将用于输出的文件Uri传递给相机 365 | takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri); 366 | //启动相机 367 | startActivityForResult(takePhotoIntent, TAKE_PHOTO); 368 | } 369 | } 370 | } 371 | 372 | @Override 373 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 374 | super.onActivityResult(requestCode, resultCode, data); 375 | if (resultCode == RESULT_OK && requestCode == TAKE_PHOTO) { 376 | //缩略图信息是储存在返回的intent中的Bundle中的,对应Bundle中的键为data,因此从Intent中取出 Bundle再根据data取出来Bitmap即可 377 | // Bundle extras = data.getExtras(); 378 | // Bitmap bitmap = (Bitmap) extras.get("data"); 379 | // BitmapFactory.decodeFile(this.getContentResolver().) 380 | // galleryAddPictures(mImageUri); 381 | // getSupportLoaderManager().restartLoader(0, null, mLoaderCallbacks); 382 | galleryAddPictures(); 383 | try { 384 | Bitmap bitmap = BitmapFactory.decodeStream(this.getContentResolver().openInputStream(mImageUri)); 385 | Log.i("take photo", bitmap + ""); 386 | } catch (FileNotFoundException e) { 387 | e.printStackTrace(); 388 | } 389 | 390 | } 391 | } 392 | 393 | 394 | private File createImageFile() { 395 | String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.CHINA).format(new Date()); 396 | String imageFileName = "IMG_" + timeStamp + "_"; 397 | File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); 398 | File imageFile = null; 399 | try { 400 | imageFile = File.createTempFile(imageFileName, ".jpg", storageDir); 401 | } catch (IOException e) { 402 | e.printStackTrace(); 403 | } 404 | return imageFile; 405 | } 406 | 407 | /** 408 | * 将拍的照片添加到相册 409 | */ 410 | private void galleryAddPictures() { 411 | //把文件插入到系统图库 412 | try { 413 | MediaStore.Images.Media.insertImage(this.getContentResolver(), 414 | takePhotoImageFile.getAbsolutePath(), takePhotoImageFile.getName(), null); 415 | } catch (FileNotFoundException e) { 416 | e.printStackTrace(); 417 | } 418 | //通知图库更新 419 | Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(takePhotoImageFile)); 420 | sendBroadcast(mediaScanIntent); 421 | } 422 | } 423 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/ui/adapter/ImageAdapter.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.ui.adapter; 2 | 3 | import android.content.Context; 4 | import android.text.TextUtils; 5 | import android.view.View; 6 | import android.widget.ImageView; 7 | 8 | import com.steven.selectimage.GlideApp; 9 | import com.steven.selectimage.R; 10 | import com.steven.selectimage.model.Image; 11 | import com.steven.selectimage.ui.SelectImageActivity; 12 | import com.steven.selectimage.widget.recyclerview.CommonRecycleAdapter; 13 | import com.steven.selectimage.widget.recyclerview.CommonViewHolder; 14 | import com.steven.selectimage.widget.recyclerview.MultiTypeSupport; 15 | 16 | import java.util.List; 17 | 18 | /** 19 | * Description: 20 | * Data:9/4/2018-3:14 PM 21 | * 22 | * @author yanzhiwen 23 | */ 24 | public class ImageAdapter extends CommonRecycleAdapter { 25 | private Context mContext; 26 | private onSelectImageCountListener mSelectImageCountListener; 27 | private List mSelectImages; 28 | 29 | public ImageAdapter(Context context, List images, List selectedImages, MultiTypeSupport typeSupport) { 30 | super(context, images, typeSupport); 31 | this.mContext = context; 32 | this.mSelectImages = selectedImages; 33 | } 34 | 35 | @Override 36 | protected void convert(CommonViewHolder holder, final Image image, int position) { 37 | if (!TextUtils.isEmpty(image.getPath())) { 38 | final ImageView chb_selected = holder.getView(R.id.iv_selected); 39 | final View maskView = holder.getView(R.id.mask); 40 | ImageView iv_image = holder.getView(R.id.iv_image); 41 | GlideApp.with(mContext).load(image.getPath()).into(iv_image); 42 | chb_selected.setOnClickListener(v -> { 43 | if (image.isSelect()) { 44 | image.setSelect(false); 45 | mSelectImages.remove(image); 46 | chb_selected.setSelected(false); 47 | } else if (mSelectImages.size() < SelectImageActivity.MAX_SIZE) { 48 | image.setSelect(true); 49 | mSelectImages.add(image); 50 | chb_selected.setSelected(true); 51 | maskView.setVisibility(image.isSelect() ? View.VISIBLE : View.GONE); 52 | } 53 | if (mSelectImageCountListener != null) { 54 | mSelectImageCountListener.onSelectImageCount(mSelectImages.size()); 55 | mSelectImageCountListener.onSelectImageList(mSelectImages); 56 | } 57 | }); 58 | chb_selected.setSelected(image.isSelect()); 59 | maskView.setVisibility(image.isSelect() ? View.VISIBLE : View.GONE); 60 | } else { 61 | holder.getView(R.id.iv_camera).setOnClickListener(v -> { 62 | if (mOnCameraClickListener != null) { 63 | mOnCameraClickListener.onCameraClick(); 64 | } 65 | }); 66 | } 67 | } 68 | 69 | 70 | public void setSelectImageCountListener(onSelectImageCountListener selectImageCountListener) { 71 | mSelectImageCountListener = selectImageCountListener; 72 | } 73 | 74 | public interface onSelectImageCountListener { 75 | void onSelectImageCount(int count); 76 | 77 | void onSelectImageList(List images); 78 | } 79 | 80 | private onCameraClickListener mOnCameraClickListener; 81 | 82 | public void setOnCameraClickListener(onCameraClickListener onCameraClickListener) { 83 | this.mOnCameraClickListener = onCameraClickListener; 84 | } 85 | 86 | public interface onCameraClickListener { 87 | void onCameraClick(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/ui/adapter/ImageFolderAdapter.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.ui.adapter; 2 | 3 | import android.content.Context; 4 | import android.widget.ImageView; 5 | 6 | import com.steven.selectimage.GlideApp; 7 | import com.steven.selectimage.R; 8 | import com.steven.selectimage.widget.recyclerview.CommonRecycleAdapter; 9 | import com.steven.selectimage.widget.recyclerview.CommonViewHolder; 10 | import com.steven.selectimage.model.ImageFolder; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * Description: 16 | * Data:9/4/2018-3:14 PM 17 | * 18 | * @author yanzhiwen 19 | */ 20 | public class ImageFolderAdapter extends CommonRecycleAdapter { 21 | private Context mContext; 22 | 23 | public ImageFolderAdapter(Context context, List imageFolders, int layoutId) { 24 | super(context, imageFolders, layoutId); 25 | this.mContext = context; 26 | } 27 | 28 | @Override 29 | protected void convert(CommonViewHolder holder, ImageFolder imageFolder, int potion) { 30 | holder.setText(R.id.tv_folder_name, imageFolder.getName()) 31 | .setText(R.id.tv_size, imageFolder.getImages().size() + "张"); 32 | ImageView iv_folder = holder.getView(R.id.iv_folder); 33 | GlideApp.with(mContext) 34 | .load(imageFolder.getAlbumPath()) 35 | .into(iv_folder); 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/ui/adapter/SelectedImageAdapter.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.ui.adapter; 2 | 3 | import android.content.Context; 4 | import android.widget.ImageView; 5 | 6 | import com.steven.selectimage.GlideApp; 7 | import com.steven.selectimage.R; 8 | import com.steven.selectimage.model.Image; 9 | import com.steven.selectimage.widget.recyclerview.CommonRecycleAdapter; 10 | import com.steven.selectimage.widget.recyclerview.CommonViewHolder; 11 | 12 | import java.util.ArrayList; 13 | 14 | /** 15 | * Description: 16 | * Data:9/4/2018-3:14 PM 17 | * 18 | * @author yanzhiwen 19 | */ 20 | public class SelectedImageAdapter extends CommonRecycleAdapter { 21 | private Context mContext; 22 | 23 | public SelectedImageAdapter(Context context, ArrayList data, int layoutId) { 24 | super(context, data, layoutId); 25 | this.mContext = context; 26 | } 27 | 28 | @Override 29 | protected void convert(CommonViewHolder holder, Image image, int position) { 30 | ImageView iv = holder.getView(R.id.iv_selected_image); 31 | GlideApp.with(mContext) 32 | .load(image.getPath()) 33 | .centerCrop() 34 | .into(iv); 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/utils/BitmapUtil.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.utils; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.util.Log; 6 | 7 | /** 8 | * Description: 9 | * Data:3/26/2019-5:43 PM 10 | * 11 | * @author yanzhiwen 12 | */ 13 | public class BitmapUtil { 14 | public static Bitmap calculateInSampleSize(String imagePath) { 15 | // 设置参数 16 | BitmapFactory.Options options = new BitmapFactory.Options(); 17 | options.inJustDecodeBounds = true; // 只获取图片的大小信息,而不是将整张图片载入在内存中,避免内存溢出 18 | // 输出图像数据 19 | //orginBitmap=: size: 14745600 width: 1440 heigth:2560 20 | // Bitmap originBitmap = BitmapFactory.decodeFile(imagePath, options); 21 | // Log.w("originBitmap=", "size: " + originBitmap.getByteCount() + 22 | // " width: " + originBitmap.getWidth() + " height:" + originBitmap.getHeight()); 23 | int height = options.outHeight; 24 | int width = options.outWidth; 25 | int inSampleSize = 2; // 默认像素压缩比例,压缩为原图的1/2 26 | int minLen = Math.min(height, width); // 原图的最小边长 27 | if (minLen > 100) { // 如果原始图像的最小边长大于100dp(此处单位我认为是dp,而非px) 28 | float ratio = (float) minLen / 100.0f; // 计算像素压缩比例 29 | inSampleSize = (int) ratio; 30 | } 31 | options.inSampleSize = inSampleSize; // 设置为刚才计算的压缩比例 32 | options.inJustDecodeBounds = false; // 计算好压缩比例后,这次可以去加载原图了 33 | Bitmap bm = BitmapFactory.decodeFile(imagePath, options); // 解码文件 34 | //size: 74256 width: 102 heigth:182 35 | Log.w("bm=", "size: " + bm.getByteCount() + " width: " + bm.getWidth() + " heigth:" + bm.getHeight()); // 输出图像数据 36 | return bm; 37 | } 38 | 39 | 40 | /** 41 | * @param reqWidth 要求的宽 42 | * @param reqHeight 要求的高 43 | */ 44 | public static Bitmap decodeSampledBitmapFromResource(String path, int reqWidth, int reqHeight) { 45 | //Options 只保存图片尺寸大小,不保存图片到内存 46 | BitmapFactory.Options options = new BitmapFactory.Options(); 47 | // 设置该属性为true,不加载图片到内存,只返回图片的宽高到options中。 48 | // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小 49 | options.inJustDecodeBounds = true; 50 | //先加载图片 51 | BitmapFactory.decodeFile(path, options); 52 | options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 53 | // 重新设置该属性为false,加载图片返回 54 | // 使用获取到的inSampleSize值再次解析图片 55 | options.inJustDecodeBounds = false; 56 | return BitmapFactory.decodeFile(path, options); 57 | } 58 | 59 | private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { 60 | int width = options.outWidth; 61 | int height = options.outHeight; 62 | int inSampleSize = 1; 63 | int widthRatio = Math.round((float) width / (float) reqWidth); 64 | int heightRatio = Math.round((float) height / (float) reqHeight); 65 | inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; 66 | return inSampleSize; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/utils/StatusBarUtil.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.utils; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.graphics.Color; 7 | import android.os.Build; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.view.Window; 11 | import android.view.WindowManager; 12 | 13 | import java.lang.reflect.Field; 14 | import java.lang.reflect.Method; 15 | 16 | /** 17 | * 状态栏工具类(沉浸式) 18 | */ 19 | public class StatusBarUtil { 20 | 21 | /**设置状态栏透明与字体颜色*/ 22 | public static void setStatusBarTrans(Activity activity, boolean lightStatusBar) { 23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 24 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 25 | }else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 26 | Window window = activity.getWindow(); 27 | window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 28 | window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 29 | | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); 30 | window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 31 | window.setStatusBarColor(Color.TRANSPARENT); 32 | } 33 | 34 | ILightStatusBar IMPL; 35 | if (MIUILightStatusBarImpl.isMe()) { 36 | IMPL = new MIUILightStatusBarImpl(); 37 | } else if (MeizuLightStatusBarImpl.isMe()) { 38 | IMPL = new MeizuLightStatusBarImpl(); 39 | } else { 40 | IMPL = new ILightStatusBar() { 41 | @Override 42 | public void setLightStatusBar(Window window, boolean lightStatusBar) { 43 | } 44 | }; 45 | } 46 | IMPL.setLightStatusBar(activity.getWindow(), lightStatusBar); 47 | } 48 | 49 | /**小米状态栏设置类*/ 50 | public static class MIUILightStatusBarImpl implements ILightStatusBar { 51 | static boolean isMe() { 52 | return "Xiaomi".equals(Build.MANUFACTURER); 53 | } 54 | 55 | public void setLightStatusBar(Window window, boolean lightStatusBar) { 56 | Class clazz = window.getClass(); 57 | try { 58 | Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); 59 | Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); 60 | int darkModeFlag = field.getInt(layoutParams); 61 | Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); 62 | extraFlagField.invoke(window, lightStatusBar ? darkModeFlag : 0, darkModeFlag); 63 | } catch (Exception e) { 64 | } 65 | } 66 | } 67 | 68 | /**魅族状态栏设置类*/ 69 | public static class MeizuLightStatusBarImpl implements ILightStatusBar { 70 | static boolean isMe() { 71 | final Method method; 72 | try { 73 | method = Build.class.getMethod("hasSmartBar"); 74 | return method != null; 75 | } catch (NoSuchMethodException e) { 76 | } 77 | return false; 78 | } 79 | 80 | public void setLightStatusBar(Window window, boolean lightStatusBar) { 81 | WindowManager.LayoutParams params = window.getAttributes(); 82 | try { 83 | Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON"); 84 | Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags"); 85 | darkFlag.setAccessible(true); 86 | meizuFlags.setAccessible(true); 87 | int bit = darkFlag.getInt(null); 88 | int value = meizuFlags.getInt(params); 89 | if (lightStatusBar) { 90 | value |= bit; 91 | } else { 92 | value &= ~bit; 93 | } 94 | meizuFlags.setInt(params, value); 95 | window.setAttributes(params); 96 | darkFlag.setAccessible(false); 97 | meizuFlags.setAccessible(false); 98 | } catch (Exception e) { 99 | } 100 | } 101 | } 102 | 103 | interface ILightStatusBar { 104 | 105 | void setLightStatusBar(Window window, boolean lightStatusBar); 106 | } 107 | 108 | /** 109 | * 设置状态栏的颜色 110 | */ 111 | @TargetApi(19) 112 | public static void statusBarTintColor(Activity activity, int color) { 113 | // 代表 5.0 及以上 114 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 115 | activity.getWindow().setStatusBarColor(color); 116 | return; 117 | } 118 | 119 | // versionCode > 4.4 and versionCode < 5.0 120 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 121 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 122 | // 在原来的位置上添加一个状态栏 123 | View statusBarView = createStatusBarView(activity); 124 | ViewGroup androidContainer = (ViewGroup ) activity.getWindow().getDecorView(); 125 | androidContainer = (ViewGroup ) androidContainer.getChildAt(0); 126 | androidContainer.addView(statusBarView, 0); 127 | statusBarView.setBackgroundColor(color); 128 | } 129 | } 130 | 131 | /** 132 | * 创建一个需要填充statusBarView 133 | */ 134 | private static View createStatusBarView(Activity activity) { 135 | View statusBarView = new View(activity); 136 | ViewGroup.LayoutParams statusBarParams = new ViewGroup.LayoutParams( 137 | ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); 138 | statusBarView.setLayoutParams(statusBarParams); 139 | return statusBarView; 140 | } 141 | 142 | /** 143 | * 获取状态栏的高度 144 | */ 145 | public static int getStatusBarHeight(Context context) { 146 | int result = 0; 147 | int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); 148 | if (resourceId > 0) { 149 | result = context.getResources().getDimensionPixelSize(resourceId); 150 | } 151 | return result; 152 | } 153 | 154 | 155 | /** 156 | * 状态栏透明,整个界面全屏 157 | */ 158 | public static void statusBarTranslucent(Activity activity) { 159 | // 代表 5.0 及以上 160 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 161 | View decorView = activity.getWindow().getDecorView(); 162 | int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; 163 | decorView.setSystemUiVisibility(option); 164 | activity.getWindow().setStatusBarColor(Color.TRANSPARENT); 165 | return; 166 | } 167 | // versionCode > 4.4 and versionCode < 5.0 168 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 169 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/utils/TDevice.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.utils; 2 | 3 | import android.content.res.Resources; 4 | import android.util.DisplayMetrics; 5 | import android.util.TypedValue; 6 | 7 | /** 8 | * Description: 9 | * Data:9/4/2018-5:05 PM 10 | * 11 | * @author yanzhiwen 12 | */ 13 | public class TDevice { 14 | 15 | /** 16 | * Change SP to PX 17 | * 18 | * @param resources Resources 19 | * @param sp SP 20 | * @return PX 21 | */ 22 | public static float spToPx(Resources resources, float sp) { 23 | DisplayMetrics metrics = resources.getDisplayMetrics(); 24 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, metrics); 25 | } 26 | 27 | /** 28 | * Change Dip to PX 29 | * 30 | * @param resources Resources 31 | * @param dp Dip 32 | * @return PX 33 | */ 34 | public static float dipToPx(Resources resources, float dp) { 35 | DisplayMetrics metrics = resources.getDisplayMetrics(); 36 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics); 37 | } 38 | 39 | 40 | public static float pxTodip(Resources resources, float px) { 41 | DisplayMetrics metrics = resources.getDisplayMetrics(); 42 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, px, metrics); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/widget/ImageFolderView.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.widget; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.AnimatorSet; 6 | import android.animation.ObjectAnimator; 7 | import android.content.Context; 8 | import android.graphics.Color; 9 | import android.support.annotation.Nullable; 10 | import android.support.v7.widget.LinearLayoutManager; 11 | import android.support.v7.widget.RecyclerView; 12 | import android.util.AttributeSet; 13 | import android.view.Gravity; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.view.animation.AccelerateDecelerateInterpolator; 17 | import android.widget.FrameLayout; 18 | 19 | import com.steven.selectimage.R; 20 | import com.steven.selectimage.model.ImageFolder; 21 | import com.steven.selectimage.widget.recyclerview.OnItemClickListener; 22 | import com.steven.selectimage.ui.adapter.ImageFolderAdapter; 23 | 24 | import java.util.List; 25 | 26 | /** 27 | * Description: 28 | * Data:9/5/2018-1:12 PM 29 | * 30 | * @author yanzhiwen 31 | */ 32 | public class ImageFolderView extends FrameLayout implements OnItemClickListener { 33 | private View mShadowView; 34 | private String mShadowViewColor = "#50000000"; 35 | private RecyclerView mImageFolderRv; 36 | private List mImageFolders; 37 | private ImageFolderViewListener mListener; 38 | private int mImageFolderHeight; 39 | private boolean mShow; 40 | 41 | public ImageFolderView(Context context) { 42 | this(context, null); 43 | } 44 | 45 | public ImageFolderView(Context context, @Nullable AttributeSet attrs) { 46 | this(context, attrs, 0); 47 | } 48 | 49 | public ImageFolderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 50 | super(context, attrs, defStyleAttr); 51 | mShadowView = new View(context); 52 | mShadowView.setBackgroundColor(Color.parseColor(mShadowViewColor)); 53 | mImageFolderRv = (RecyclerView) inflate(context, R.layout.image_folder_layout, null); 54 | //设置LayoutParams 55 | FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 56 | FrameLayout.LayoutParams.MATCH_PARENT); 57 | layoutParams.gravity = Gravity.BOTTOM; 58 | mImageFolderRv.setLayoutParams(layoutParams); 59 | //设置布局管理器setLayoutManager 60 | mImageFolderRv.setLayoutManager(new LinearLayoutManager(context)); 61 | addView(mShadowView); 62 | addView(mImageFolderRv); 63 | //开始不显示阴影 64 | mShadowView.setAlpha(0f); 65 | mShadowView.setVisibility(GONE); 66 | 67 | } 68 | 69 | public void setImageFolders(List imageFolders) { 70 | mImageFolders = imageFolders; 71 | } 72 | 73 | public void setAdapter(ImageFolderAdapter adapter) { 74 | if (adapter == null) { 75 | throw new NullPointerException("adapter not null!"); 76 | } 77 | mImageFolderRv.setAdapter(adapter); 78 | adapter.setItemClickListener(this); 79 | } 80 | 81 | public void setListener(ImageFolderViewListener listener) { 82 | this.mListener = listener; 83 | } 84 | 85 | /** 86 | * 显示 87 | */ 88 | public void show() { 89 | if (mShow) { 90 | return; 91 | } 92 | if (mListener != null) { 93 | mListener.onShow(); 94 | } 95 | mShow = true; 96 | mShadowView.setVisibility(VISIBLE); 97 | ObjectAnimator translationYAnimator = ObjectAnimator.ofFloat(mImageFolderRv, "translationY", mImageFolderHeight, 0); 98 | translationYAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); 99 | ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(mShadowView, "alpha", 0f, 1f); 100 | AnimatorSet animatorSet = new AnimatorSet(); 101 | animatorSet.playTogether(translationYAnimator, alphaAnimator); 102 | animatorSet.setDuration(388); 103 | animatorSet.start(); 104 | 105 | } 106 | 107 | /** 108 | * 隐藏 109 | */ 110 | public void hide() { 111 | if (!mShow) { 112 | return; 113 | } 114 | if (mListener != null) { 115 | mListener.onDismiss(); 116 | } 117 | ObjectAnimator translationYAnimator = ObjectAnimator.ofFloat(mImageFolderRv, 118 | "translationY", 0, mImageFolderHeight); 119 | translationYAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); 120 | ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(mShadowView, "alpha", 1f, 0f); 121 | AnimatorSet animatorSet = new AnimatorSet(); 122 | animatorSet.playTogether(translationYAnimator, alphaAnimator); 123 | animatorSet.setDuration(388); 124 | animatorSet.start(); 125 | animatorSet.addListener(new AnimatorListenerAdapter() { 126 | @Override 127 | public void onAnimationEnd(Animator animation) { 128 | super.onAnimationEnd(animation); 129 | mShow = false; 130 | mShadowView.setVisibility(GONE); 131 | } 132 | }); 133 | } 134 | 135 | 136 | @Override 137 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 138 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 139 | //获取高度 140 | int height = MeasureSpec.getSize(heightMeasureSpec); 141 | mImageFolderHeight = (int) (height * 0.9f); 142 | ViewGroup.LayoutParams params = mImageFolderRv.getLayoutParams(); 143 | params.height = mImageFolderHeight; 144 | mImageFolderRv.setLayoutParams(params); 145 | measureChild(mImageFolderRv, widthMeasureSpec, heightMeasureSpec); 146 | //开始的时候,移下去 147 | mImageFolderRv.setTranslationY(mImageFolderHeight); 148 | } 149 | 150 | public boolean isShowing() { 151 | return mShow; 152 | } 153 | 154 | 155 | @Override 156 | public void onItemClick(int position) { 157 | if (mListener != null) { 158 | mListener.onSelectFolder(this, mImageFolders.get(position)); 159 | hide(); 160 | } 161 | } 162 | 163 | public interface ImageFolderViewListener { 164 | void onSelectFolder(ImageFolderView imageFolderView, ImageFolder imageFolder); 165 | 166 | void onDismiss(); 167 | 168 | void onShow(); 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/widget/PreViewImageView.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.widget; 2 | 3 | import android.animation.FloatEvaluator; 4 | import android.animation.ValueAnimator; 5 | import android.content.Context; 6 | import android.graphics.Canvas; 7 | import android.graphics.drawable.Drawable; 8 | import android.support.v7.widget.AppCompatImageView; 9 | import android.util.AttributeSet; 10 | import android.util.Log; 11 | import android.view.GestureDetector; 12 | import android.view.MotionEvent; 13 | import android.view.ScaleGestureDetector; 14 | import android.view.animation.AccelerateInterpolator; 15 | 16 | /** 17 | * Description: PreViewImageView 预览图片 手指缩放 移动 18 | * Data:11/27/2018-2:11 PM 19 | * 20 | * @author yanzhiwen 21 | */ 22 | public class PreViewImageView extends AppCompatImageView { 23 | private static final String TAG = PreViewImageView.class.getSimpleName(); 24 | private GestureDetector mGestureDetector; 25 | private ScaleGestureDetector mScaleGestureDetector; 26 | private int mBoundWidth = 0; 27 | private int mBoundHeight = 0; 28 | private boolean isAutoScale = false; 29 | private float scale = 1.0f; 30 | private float translateLeft = 0.0f; 31 | private float translateTop = 0.0f; 32 | private static final float mMaxScale = 4.0f; 33 | private static final float mMinScale = 0.4f; 34 | 35 | private ValueAnimator mScaleAnimator; 36 | private ValueAnimator mHorizontalXAnimator; 37 | private ValueAnimator mVerticalYAnimator; 38 | 39 | private AccelerateInterpolator mAccelerateInterpolator = new AccelerateInterpolator(); 40 | private FloatEvaluator mFloatEvaluator = new FloatEvaluator(); 41 | 42 | public PreViewImageView(Context context) { 43 | this(context,null); 44 | } 45 | 46 | public PreViewImageView(Context context, AttributeSet attrs) { 47 | this(context, attrs,0); 48 | } 49 | 50 | public PreViewImageView(Context context, AttributeSet attrs, int defStyleAttr) { 51 | super(context, attrs, defStyleAttr); 52 | mGestureDetector = new GestureDetector(getContext(), new GestureListener()); 53 | mScaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureListener()); 54 | } 55 | 56 | //手势处理类GestureDetector.SimpleOnGestureListener 57 | private class GestureListener extends GestureDetector.SimpleOnGestureListener { 58 | /** 59 | * 手指滑动调用 60 | * 61 | * @param e1 horizontal event 62 | * @param e2 vertical event 63 | * @param distanceX previous X - current X, toward left , is position 64 | * @param distanceY previous Y - current Y, toward up, is position 65 | * @return true 66 | */ 67 | @Override 68 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 69 | final float mScaledWidth = mBoundWidth * scale; 70 | final float mScaledHeight = mBoundHeight * scale; 71 | if (mScaledHeight > getHeight()) { 72 | translateTop -= distanceY * 1.5; 73 | translateTop = getTranslateTop(translateTop); 74 | } 75 | boolean isReachBorder = false; 76 | if (mScaledWidth > getWidth()) { 77 | translateLeft -= distanceX * 1.5; 78 | final float t = getTranslateLeft(translateLeft); 79 | if (t != translateLeft) isReachBorder = true; 80 | translateLeft = t; 81 | } else { 82 | isReachBorder = true; 83 | } 84 | invalidate(); 85 | return true; 86 | } 87 | 88 | /** 89 | * 手指双击调用 90 | * 91 | * @param e event 92 | * @return true 93 | */ 94 | @Override 95 | public boolean onDoubleTap(MotionEvent e) { 96 | isAutoScale = true; 97 | ValueAnimator scaleAnimator = getResetScaleAnimator(); 98 | if (scale == 1.0f) { 99 | //缩放 100 | scaleAnimator.setFloatValues(1.0f, 2.0f); 101 | //x轴平移 102 | ValueAnimator xAnimator = getHorizontalXAnimator(); 103 | //y轴平移 104 | ValueAnimator yAnimator = getVerticalYAnimator(); 105 | xAnimator.setFloatValues(translateLeft, (getWidth() - mBoundWidth * 2.f) / 2.f);//-540 106 | Log.i(TAG, "onDoubleTap: translateLeft " + (getWidth() - mBoundWidth * 2.f) / 2.f); 107 | yAnimator.setFloatValues(translateTop, getDefaultTranslateTop(getHeight(), mBoundHeight * 2)); 108 | Log.i(TAG, "onDoubleTap: translateTop" + getDefaultTranslateTop(getHeight(), mBoundHeight * 2)); //0 109 | xAnimator.addUpdateListener(getOnTranslateXAnimationUpdate()); 110 | yAnimator.addUpdateListener(getOnTranslateYAnimationUpdate()); 111 | //开启动画 112 | xAnimator.start(); 113 | yAnimator.start(); 114 | } else { 115 | scaleAnimator.setFloatValues(scale, 1.0f); 116 | resetDefaultState(); 117 | 118 | } 119 | scaleAnimator.addUpdateListener(getOnScaleAnimationUpdate()); 120 | scaleAnimator.start(); 121 | return true; 122 | } 123 | 124 | @Override 125 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 126 | return super.onFling(e1, e2, velocityX, velocityY); 127 | } 128 | } 129 | 130 | //手指缩放类ScaleGestureDetector.SimpleOnScaleGestureListener 131 | private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { 132 | /** 133 | * 两个手指缩放调用 134 | * 135 | * @param detector ScaleGestureDetector 136 | * @return true 137 | */ 138 | @Override 139 | public boolean onScale(ScaleGestureDetector detector) { 140 | final float mOldScaledWidth = mBoundWidth * scale; 141 | final float mOldScaledHeight = mBoundHeight * scale; 142 | if (mOldScaledWidth > getWidth() && getDiffX() != 0 || 143 | (mOldScaledHeight > getHeight() && getDiffY() != 0)) return false; 144 | float factor = detector.getScaleFactor(); 145 | Log.i(TAG, "factor=" + factor); 146 | float value = scale; 147 | value += (factor - 1) * 2; 148 | if (value == scale) return true; 149 | if (value <= mMinScale) return false; 150 | if (value > mMaxScale) return false; 151 | scale = value; 152 | final float mScaledWidth = mBoundWidth * scale; 153 | final float mScaledHeight = mBoundHeight * scale; 154 | 155 | // 走了些弯路, 不应该带入translateX计算, 因为二次放大之后计算就不正确了,它应该受scale的制约 156 | translateLeft = getWidth() / 2.f - (getWidth() / 2.f - translateLeft) * mScaledWidth / mOldScaledWidth; 157 | translateTop = getHeight() / 2.f - (getHeight() / 2.f - translateTop) * mScaledHeight / mOldScaledHeight; 158 | 159 | final float diffX = getDiffX(); 160 | final float diffY = getDiffY(); 161 | 162 | // 考虑宽图, 如果缩小的时候图片左边界到了屏幕左边界,停留在左边界缩小 163 | if (diffX > 0 && mScaledWidth > getWidth()) { 164 | translateLeft = 0; 165 | } 166 | // 右边界问题 167 | if (diffX < 0 && mScaledWidth > getWidth()) { 168 | translateLeft = getWidth() - mScaledWidth; 169 | } 170 | 171 | // 考虑到长图,上边界问题 172 | if (diffY > 0 && mScaledHeight > getHeight()) { 173 | translateTop = 0; 174 | } 175 | 176 | // 下边界问题 177 | if (diffY < 0 && mScaledHeight > getHeight()) { 178 | translateTop = getHeight() - mScaledHeight; 179 | } 180 | 181 | invalidate(); 182 | return true; 183 | } 184 | } 185 | 186 | 187 | /** 188 | * @return 如果是正数, 左边有空隙, 如果是负数, 右边有空隙, 如果是0, 代表两边都没有空隙 189 | */ 190 | private float getDiffX() { 191 | final float mScaledWidth = mBoundWidth * scale; 192 | return translateLeft >= 0 193 | ? translateLeft 194 | : getWidth() - translateLeft - mScaledWidth > 0 195 | ? -(getWidth() - translateLeft - mScaledWidth) 196 | : 0; 197 | } 198 | 199 | /** 200 | * @return 如果是正数, 上面有空隙, 如果是负数, 下面有空隙, 如果是0, 代表两边都没有空隙 201 | */ 202 | private float getDiffY() { 203 | final float mScaledHeight = mBoundHeight * scale; 204 | return translateTop >= 0 205 | ? translateTop 206 | : getHeight() - translateTop - mScaledHeight > 0 207 | ? -(getHeight() - translateTop - mScaledHeight) 208 | : 0; 209 | } 210 | 211 | 212 | @Override 213 | protected boolean setFrame(int l, int t, int r, int b) { 214 | super.setFrame(l, t, r, b); 215 | Drawable drawable = getDrawable(); 216 | if (drawable == null) return false; 217 | if (mBoundWidth != 0 && mBoundHeight != 0 && scale != 1) return false; 218 | adjustBounds(getWidth(), getHeight()); 219 | return true; 220 | } 221 | 222 | 223 | private void adjustBounds(int width, int height) { 224 | Drawable drawable = getDrawable(); 225 | if (drawable == null) return; 226 | mBoundWidth = drawable.getBounds().width(); 227 | mBoundHeight = drawable.getBounds().height(); 228 | float scale = ( float ) mBoundWidth / width; 229 | mBoundHeight /= scale; 230 | mBoundWidth = width; 231 | drawable.setBounds(0, 0, mBoundWidth, mBoundHeight); 232 | translateLeft = 0; 233 | translateTop = getDefaultTranslateTop(height, mBoundHeight); 234 | } 235 | 236 | @Override 237 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 238 | super.onSizeChanged(w, h, oldw, oldh); 239 | adjustBounds(w, h); 240 | } 241 | 242 | 243 | @Override 244 | protected void onDraw(Canvas canvas) { 245 | super.onDraw(canvas); 246 | Drawable drawable = getDrawable(); 247 | if (drawable == null) return; 248 | //图片的宽和高 249 | int drawableWidth = drawable.getIntrinsicWidth(); 250 | int drawableHeight = drawable.getIntrinsicHeight(); 251 | if (drawableWidth == 0 || drawableHeight == 0) { 252 | return; 253 | } 254 | canvas.save(); 255 | canvas.translate(translateLeft, translateTop); 256 | canvas.scale(scale, scale); 257 | //如果先scale,再translate,那么,真实translate的值是要与scale值相乘的 258 | drawable.draw(canvas); 259 | canvas.restore(); 260 | } 261 | 262 | @Override 263 | public boolean onTouchEvent(MotionEvent event) { 264 | if (event.getAction() == MotionEvent.ACTION_DOWN) { 265 | cancelAnimator(); 266 | } 267 | mGestureDetector.onTouchEvent(event); 268 | mScaleGestureDetector.onTouchEvent(event); 269 | return true; 270 | } 271 | 272 | 273 | /** 274 | * 重置伸缩 275 | * 276 | * @return mScaleAnimator 277 | */ 278 | private ValueAnimator getResetScaleAnimator() { 279 | if (mScaleAnimator != null) { 280 | mScaleAnimator.removeAllUpdateListeners(); 281 | } else { 282 | mScaleAnimator = ValueAnimator.ofFloat(); 283 | } 284 | mScaleAnimator.setDuration(150); 285 | mScaleAnimator.setInterpolator(mAccelerateInterpolator); 286 | mScaleAnimator.setEvaluator(mFloatEvaluator); 287 | return mScaleAnimator; 288 | } 289 | 290 | 291 | /** 292 | * 水平方向的动画 (X轴) 293 | * 294 | * @return mHorizontalAnimator 295 | */ 296 | private ValueAnimator getHorizontalXAnimator() { 297 | if (mHorizontalXAnimator != null) { 298 | mHorizontalXAnimator.removeAllUpdateListeners(); 299 | } else { 300 | mHorizontalXAnimator = ValueAnimator.ofFloat(); 301 | } 302 | mHorizontalXAnimator.setDuration(150); 303 | mHorizontalXAnimator.setInterpolator(mAccelerateInterpolator); 304 | mHorizontalXAnimator.setEvaluator(mFloatEvaluator); 305 | return mHorizontalXAnimator; 306 | } 307 | 308 | 309 | /** 310 | * 垂直方向的动画 311 | * 312 | * @return resetYAnimator 313 | */ 314 | private ValueAnimator getVerticalYAnimator() { 315 | if (mVerticalYAnimator != null) { 316 | mVerticalYAnimator.removeAllUpdateListeners(); 317 | } else { 318 | mVerticalYAnimator = ValueAnimator.ofFloat(); 319 | } 320 | mVerticalYAnimator.setDuration(150); 321 | mVerticalYAnimator.setInterpolator(mAccelerateInterpolator); 322 | mVerticalYAnimator.setEvaluator(mFloatEvaluator); 323 | return mVerticalYAnimator; 324 | } 325 | 326 | /** 327 | * 重置到初始的状态 328 | */ 329 | private void resetDefaultState() { 330 | if (translateLeft != 0) { 331 | ValueAnimator mTranslateXAnimator = getHorizontalXAnimator(); 332 | mTranslateXAnimator.setFloatValues(translateLeft, 0); 333 | mTranslateXAnimator.addUpdateListener(getOnTranslateXAnimationUpdate()); 334 | mTranslateXAnimator.start(); 335 | } 336 | 337 | ValueAnimator mTranslateYAnimator = getVerticalYAnimator(); 338 | mTranslateYAnimator.setFloatValues(translateTop, getDefaultTranslateTop(getHeight(), mBoundHeight)); 339 | mTranslateYAnimator.addUpdateListener(getOnTranslateYAnimationUpdate()); 340 | mTranslateYAnimator.start(); 341 | 342 | } 343 | 344 | 345 | private float getDefaultTranslateTop(int height, int boundHeight) { 346 | float top = (height - boundHeight) / 2.f; 347 | return top > 0 ? top : 0; 348 | } 349 | 350 | 351 | private ValueAnimator.AnimatorUpdateListener onScaleAnimationUpdate; 352 | 353 | /** 354 | * 重置伸缩动画的监听器 355 | * 356 | * @return onScaleAnimationUpdate 357 | */ 358 | public ValueAnimator.AnimatorUpdateListener getOnScaleAnimationUpdate() { 359 | if (onScaleAnimationUpdate != null) return onScaleAnimationUpdate; 360 | onScaleAnimationUpdate = new ValueAnimator.AnimatorUpdateListener() { 361 | @Override 362 | public void onAnimationUpdate(ValueAnimator animation) { 363 | scale = ( float ) animation.getAnimatedValue(); 364 | invalidate(); 365 | } 366 | }; 367 | return onScaleAnimationUpdate; 368 | } 369 | 370 | /** 371 | * 水平动画的监听器 372 | * 373 | * @return 374 | */ 375 | private ValueAnimator.AnimatorUpdateListener onTranslateXAnimationUpdate; 376 | 377 | public ValueAnimator.AnimatorUpdateListener getOnTranslateXAnimationUpdate() { 378 | if (onTranslateXAnimationUpdate != null) return onTranslateXAnimationUpdate; 379 | onTranslateXAnimationUpdate = new ValueAnimator.AnimatorUpdateListener() { 380 | @Override 381 | public void onAnimationUpdate(ValueAnimator animation) { 382 | translateLeft = ( float ) animation.getAnimatedValue(); 383 | invalidate(); 384 | } 385 | }; 386 | return onTranslateXAnimationUpdate; 387 | } 388 | 389 | /** 390 | * 垂直动画的监听器 391 | * 392 | * @return onTranslateYAnimationUpdate 393 | */ 394 | private ValueAnimator.AnimatorUpdateListener onTranslateYAnimationUpdate; 395 | 396 | public ValueAnimator.AnimatorUpdateListener getOnTranslateYAnimationUpdate() { 397 | if (onTranslateYAnimationUpdate != null) return onTranslateYAnimationUpdate; 398 | onTranslateYAnimationUpdate = new ValueAnimator.AnimatorUpdateListener() { 399 | @Override 400 | public void onAnimationUpdate(ValueAnimator animation) { 401 | translateTop = ( float ) animation.getAnimatedValue(); 402 | invalidate(); 403 | } 404 | }; 405 | return onTranslateYAnimationUpdate; 406 | } 407 | 408 | /** 409 | * 向左平移的距离 410 | * 411 | * @param l translateLeft 412 | * @return l 413 | */ 414 | private float getTranslateLeft(float l) { 415 | final float mScaledWidth = mBoundWidth * scale; 416 | if (l > 0) { 417 | l = 0; 418 | } 419 | if (-l + getWidth() > mScaledWidth) { 420 | l = getWidth() - mScaledWidth; 421 | } 422 | return l; 423 | } 424 | 425 | /** 426 | * 向上平移的距离 427 | * 428 | * @param t top 429 | * @return t 430 | */ 431 | private float getTranslateTop(float t) { 432 | final float mScaledHeight = mBoundHeight * scale; 433 | if (t > 0) { 434 | t = 0; 435 | } 436 | if (-t + getHeight() > mScaledHeight) { 437 | t = getHeight() - mScaledHeight; 438 | } 439 | return t; 440 | } 441 | 442 | 443 | /** 444 | * 取消动画 445 | */ 446 | private void cancelAnimator() { 447 | if (mHorizontalXAnimator != null && mHorizontalXAnimator.isRunning()) { 448 | mHorizontalXAnimator.cancel(); 449 | } 450 | if (mVerticalYAnimator != null && mVerticalYAnimator.isRunning()) { 451 | mVerticalYAnimator.cancel(); 452 | } 453 | if (mScaleAnimator != null && mScaleAnimator.isRunning()) { 454 | mScaleAnimator.cancel(); 455 | } 456 | } 457 | } 458 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/widget/SquareFrameLayout.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.FrameLayout; 6 | 7 | /** 8 | * Description:正方形的FrameLayout容器 9 | * Data:9/4/2018-3:14 PM 10 | * 11 | * @author yanzhiwen 12 | */ 13 | public class SquareFrameLayout extends FrameLayout { 14 | public SquareFrameLayout(Context context) { 15 | this(context,null); 16 | } 17 | 18 | public SquareFrameLayout(Context context, AttributeSet attrs) { 19 | this(context, attrs,0); 20 | } 21 | 22 | public SquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { 23 | super(context, attrs, defStyleAttr); 24 | } 25 | 26 | @Override 27 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 28 | super.onMeasure(widthMeasureSpec, widthMeasureSpec); 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/widget/SquareImageView.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.ImageView; 6 | 7 | /** 8 | * Description:正方形的ImageView 9 | * Data:9/4/2018-3:15 PM 10 | * 11 | * @author yanzhiwen 12 | */ 13 | public class SquareImageView extends ImageView { 14 | public SquareImageView(Context context) { 15 | this(context,null); 16 | } 17 | 18 | public SquareImageView(Context context, AttributeSet attrs) { 19 | this(context, attrs,0); 20 | } 21 | 22 | public SquareImageView(Context context, AttributeSet attrs, int defStyleAttr) { 23 | super(context, attrs, defStyleAttr); 24 | } 25 | 26 | @Override 27 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 28 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 29 | // 自定义View 30 | int width = MeasureSpec.getSize(widthMeasureSpec); 31 | // 设置宽高为一样 32 | setMeasuredDimension(width, width); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/widget/recyclerview/CommonRecycleAdapter.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.widget.recyclerview; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Created by zhiwenyan on 5/25/17. 14 | */ 15 | 16 | public abstract class CommonRecycleAdapter extends RecyclerView.Adapter { 17 | private int mLayoutId; 18 | private List mData; 19 | private LayoutInflater mInflater; 20 | private OnItemClickListener mItemClickListener; 21 | private MultiTypeSupport mTypeSupport; 22 | 23 | 24 | public CommonRecycleAdapter(Context context, List mData, int layoutId) { 25 | this.mData = mData; 26 | this.mLayoutId = layoutId; 27 | mInflater = LayoutInflater.from(context); 28 | } 29 | 30 | public CommonRecycleAdapter(Context context, List mData, MultiTypeSupport typeSupport) { 31 | this(context, mData, -1); 32 | this.mTypeSupport = typeSupport; 33 | } 34 | 35 | @NonNull 36 | @Override 37 | public CommonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 38 | if (mTypeSupport != null) { 39 | //多布局 40 | mLayoutId = viewType; 41 | } 42 | View itemView = mInflater.inflate(mLayoutId, parent, false); 43 | return new CommonViewHolder(itemView); 44 | } 45 | 46 | @Override 47 | public void onBindViewHolder(@NonNull CommonViewHolder holder, final int position) { 48 | convert(holder, mData.get(position), position); 49 | if (mItemClickListener != null) { 50 | holder.itemView.setOnClickListener(new View.OnClickListener() { 51 | @Override 52 | public void onClick(View v) { 53 | mItemClickListener.onItemClick(position); 54 | } 55 | }); 56 | } 57 | } 58 | 59 | @Override 60 | public int getItemCount() { 61 | return mData.size(); 62 | } 63 | 64 | @Override 65 | public int getItemViewType(int position) { 66 | //多布局 67 | if (mTypeSupport != null) { 68 | return mTypeSupport.getLayoutId(mData.get(position)); 69 | } 70 | return super.getItemViewType(position); 71 | } 72 | 73 | @Override 74 | public long getItemId(int position) { 75 | return position; 76 | } 77 | 78 | //点击事件 79 | public void setItemClickListener(OnItemClickListener itemClickListener) { 80 | this.mItemClickListener = itemClickListener; 81 | } 82 | 83 | protected abstract void convert(CommonViewHolder holder, T t, int position); 84 | 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/widget/recyclerview/CommonViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.widget.recyclerview; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.util.SparseArray; 5 | import android.view.View; 6 | import android.widget.ImageView; 7 | import android.widget.TextView; 8 | 9 | /** 10 | * Created by zhiwenyan on 5/25/17. 11 | *

12 | * 通用的ViewHolder 13 | */ 14 | 15 | public class CommonViewHolder extends RecyclerView.ViewHolder { 16 | //用于缓存View 17 | private SparseArray mView; 18 | 19 | public CommonViewHolder(View itemView) { 20 | super(itemView); 21 | mView = new SparseArray<>(); 22 | } 23 | 24 | 25 | public T getView(int viewId) { 26 | View view = mView.get(viewId); 27 | if (view == null) { 28 | view = itemView.findViewById(viewId); 29 | mView.put(viewId, view); 30 | } 31 | return (T) view; 32 | } 33 | 34 | 35 | //通用的setText进行封装 36 | 37 | /** 38 | * @param viewId 39 | * @param text 40 | * @return 41 | */ 42 | public CommonViewHolder setText(int viewId, CharSequence text) { 43 | TextView tv = getView(viewId); 44 | tv.setText(text); 45 | return this; 46 | } 47 | 48 | /** 49 | * 本地图片 50 | * 51 | * @param viewId 52 | * @param resourceId 53 | * @return 54 | */ 55 | public CommonViewHolder setImageResoucrce(int viewId, int resourceId) { 56 | ImageView iv = getView(viewId); 57 | iv.setImageResource(resourceId); 58 | return this; 59 | } 60 | 61 | 62 | 63 | 64 | /** 65 | * 网络图片处理 66 | * 67 | * @param viewId 68 | * @param imageLoader 69 | * @return 70 | */ 71 | public CommonViewHolder setImagePath(int viewId, HolderImageLoader imageLoader) { 72 | ImageView iv = getView(viewId); 73 | imageLoader.loadImage(iv, imageLoader.getPath()); 74 | return this; 75 | } 76 | 77 | abstract static class HolderImageLoader { 78 | private String path; 79 | 80 | public HolderImageLoader(String path) { 81 | this.path = path; 82 | } 83 | 84 | /** 85 | * 加载图片 86 | * 87 | * @param imageView 88 | * @param path 89 | */ 90 | abstract void loadImage(ImageView imageView, String path); 91 | 92 | public String getPath() { 93 | return path; 94 | } 95 | 96 | public void setPath(String path) { 97 | this.path = path; 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/widget/recyclerview/MultiTypeSupport.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.widget.recyclerview; 2 | 3 | /** 4 | * Created by zhiwenyan on 5/25/17. 5 | * 多布局的支持 6 | */ 7 | 8 | public interface MultiTypeSupport { 9 | int getLayoutId(T item); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/widget/recyclerview/OnItemClickListener.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.widget.recyclerview; 2 | 3 | /** 4 | * Created by zhiwenyan on 5/25/17. 5 | *

6 | * 条目的点击事件 7 | */ 8 | 9 | public interface OnItemClickListener { 10 | void onItemClick(int position); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/steven/selectimage/widget/recyclerview/SpaceGridItemDecoration.java: -------------------------------------------------------------------------------- 1 | package com.steven.selectimage.widget.recyclerview; 2 | 3 | import android.graphics.Rect; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | 7 | /** 8 | * Description: 9 | * Data:9/4/2018-5:03 PM 10 | * 11 | * @author yanzhiwen 12 | */ 13 | public class SpaceGridItemDecoration extends RecyclerView.ItemDecoration { 14 | private int space; 15 | 16 | public SpaceGridItemDecoration(int space) { 17 | this.space = space; 18 | } 19 | 20 | @Override 21 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 22 | super.getItemOffsets(outRect, view, parent, state); 23 | outRect.left = space; 24 | outRect.top = space; 25 | outRect.right = space; 26 | outRect.bottom = space; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/res/anim/popup_select_image_hide.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/anim/popup_select_image_show.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |