总的来说分为两步:1拿到数据,2展示数据
先添加权限和相关依赖
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
//图片加载
implementation 'com.github.bumptech.glide:glide:4.11.0'
1.获取所有的图片数据
拿到数据之前应该先确定下图片数据在程序中存放的数据结构,由图片于是根据时间进行分组的
建立实体类
public class PhotoGroup {
private String title;
private ArrayList<Photo> photoArrayList;
// 省略 getter 和 setter 方法
}
public class Photo {
private long time;
private String thumbPath;
private String filePath;
private String fileName;
// 用于获取当前图片的格式化日期
public String getDate() {
return new SimpleDateFormat("yyyy年MM月dd日").format(new Date(time*1000L));
}
// 省略 getter 和 setter 方法
}
获取相册图片方法:
Loader:是用来加载数据的
LoaderManager:我们使用Loader加载数据时,并不直接与Loader打交道,应使用 LoaderManager 实现对 Loader 的管理
- initLoader:通过调用 LoaderManagr 的 initLoader() 方法,我们可以创建一个 Loader
- restartLoader:通过调用 LoaderManager 的 restartLoader() 方法,我们可以重启一个 Loader
- destroyLoader:通过调用 LoaderManager 的 destroyLoader() 方法我们可以销毁一个 Loader
LoaderManager.LoaderCallbacks:LoaderManager.LoaderCallbacks 是LoaderManager 中的内部接口,客户端与Loader的通信完全是事件机制,即客户端需要实现 LoaderCallbacks 中的各种回调方法,以响应Loader & LoaderManager 触发的各种事件,LoaderCallbacks 有三个回调方法需要实现
- onCreateLoader: 在 onCreateLoader() 方法内返回一个 Loader 的实例对象。很多情况下,需要查询 ContentProvider 里面的内容,那么就需要在 onCreateLoader 中返回一个 CursorLoader 的实例,CursorLoader 继承自 Loader。当然,如果 CursorLoader 不能满足我们的需求,我们可以自己编写自己的 Loade r然后在此 onCreateLoader 方法中返回
- onLoadFinished:当onCreateLoader中创建的 Loader 完成数据加载的时候,我们会在 onLoadFinished 回调函数中得到加载的数据。在此方法中,客户端可以得到数据并加以使用,在这之前,如果客户端已经保存了一份老的数据,那么需要释放对老数据的引用
- onLoaderReset:当之前创建的 Loader 被销毁(且该 Loader 向客户端发送过数据)的时候,就会触发
public class ImageVideoLoader implements LoaderManager.LoaderCallbacks<Cursor> {
private Context context;
//用于存放照片数据
private ArrayList<PhotoGroup> photoGroupList = new ArrayList<PhotoGroup>();
//回掉接口
private ImageFinishCallback imageFinishCallback;
//构造方法
public ImageVideoLoader(Context context, ImageFinishCallback imageFinishCallback){
this.context = context;
this.imageFinishCallback = imageFinishCallback;
}
//用于获取图片信息List
public ArrayList<PhotoGroup> getPhotoGroupList() {
return photoGroupList;
}
@NonNull
@Override
public Loader onCreateLoader(int id, @Nullable Bundle args) {
//存放需要查询的数据列字段名
String[] STORE_IMAGES = {
MediaStore.Images.Media.DATA,
MediaStore.Images.Media.DATE_ADDED,
MediaStore.Images.Thumbnails.DATA
};
CursorLoader cursorLoader = new CursorLoader(
context,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,//指定缩略图数据库的Uri
STORE_IMAGES,//指定所要查询的字段
null,//查询条件
null,//查询条件中问号对应的值
MediaStore.Images.Media.DATE_ADDED + " COLLATE LOCALIZED desc");//排序规则,根据MediaStore.Images.Media.DATE_ADDED(时间) 列数据进行降序排序,使得获取到的照片数据由最近到时间最久远排序
return cursorLoader;
}
// 查询数据库完成后回掉
@Override
public void onLoadFinished(@NonNull Loader loader, Cursor cursor) {
if(cursor.moveToNext()){
int thumbPathIndex = cursor.getColumnIndex(MediaStore.Images.Thumbnails.DATA);
int timeIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED);
int pathIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
long lastDate = cursor.getLong(timeIndex);//获得第一个时间
ArrayList<Photo> photoList = new ArrayList<>();
do {
String thumbPath = cursor.getString(thumbPathIndex);
long date = cursor.getLong(timeIndex);
String filepath = cursor.getString(pathIndex);
if (!getDate(lastDate).equals(getDate(date))){
//时间不同就是另一组
lastDate = date;
PhotoGroup photoGroup = new PhotoGroup();
photoGroup.setTitle(photoList.get(0).getDate());
photoGroup.setPhotoArrayList(photoList);
photoGroupList.add(photoGroup);
photoList = new ArrayList<>();
}
Photo photo = new Photo();
File f = new File(filepath);
photo.setTime(date);
photo.setThumbPath(thumbPath);
photo.setFilePath(filepath);
photo.setFileName(f.getName());
photoList.add(photo);
}while (cursor.moveToNext());
// 由于是dowhile,所以最后一组需要添加
PhotoGroup photoGroup = new PhotoGroup();
photoGroup.setTitle(photoList.get(0).getDate());
photoGroup.setPhotoArrayList(photoList);
photoGroupList.add(photoGroup);
}
//将所有数据插入 photoGroupList 后回掉进行界面显示
imageFinishCallback.imageOnLoadFinished();
}
//格式化时间
private String getDate(long time) {
return new SimpleDateFormat("yyyy年MM月dd日")
.format(new Date(time*1000L));
}
@Override
public void onLoaderReset(@NonNull Loader loader) {}
//回掉接口
public interface ImageFinishCallback{
public void imageOnLoadFinished();
}
}
新建一个Activaty 用于显示图片数据
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
private ArrayList<PhotoGroup> fileInfo = new ArrayList<PhotoGroup>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.d("zzz", "动态授权");
//动态检查权限
checkAuthority();
}else {
initView();
}
}
private void initView(){
//当数据获取完成后回掉方法
ImageVideoLoader.ImageFinishCallback imageFinishCallback = new ImageVideoLoader.ImageFinishCallback() {
@Override
public void imageOnLoadFinished() {
//最终 ImageVideoLoader 类获取到的数据
fileInfo = imageVideoLoader.getPhotoGroupList();
}
};
// 实例化
imageVideoLoader = new ImageVideoLoader(view.getContext(), imageFinishCallback);
// 获取图片数据
getLoaderManager().initLoader(0,null, imageVideoLoader);
}
//检查权限
private void checkAuthority(){
int hasWritePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
int hasReadPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
List<String> permissions = new ArrayList<String>();
if (hasWritePermission != PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (hasReadPermission != PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
}
if (!permissions.isEmpty()) {
ActivityCompat.requestPermissions(this, permissions.toArray(new String[permissions.size()]), 1);
}else {
Log.d("zzz", "已经授权");
}
}
//授权回掉
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode){
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
Log.d("zzz", "授权成功");
}else {
Toast.makeText(this, "缺失必要权限"+grantResults.length+"::"+grantResults[0], Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
break;
}
}
}
这里就将图片数据获取到 fileInfo List中了
2.显示界面
采用嵌套 RecyclerView 方法实现
main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/main_recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
image_group_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/image_item_cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:cardCornerRadius="4dp"
app:cardElevation="0dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/tupian_item_recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
image_child_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/image_item_cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_margin="2dp"
app:cardCornerRadius="4dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/image_item_imageView"
android:layout_width="match_parent"
android:layout_height="100dp"
android:scaleType="centerCrop"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
3.添加适配
public class PhotoAdapter extends RecyclerView.Adapter<PhotoAdapter.ViewHolder> {
private Context context;
private ArrayList<PhotoGroup> photoGroupList;
// 构造方法(将图片数据传入)
public PhotoAdapter dapter(ArrayList<PhotoGroup> photoGroupList){
this.photoGroupList = photoGroupList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (context == null){
context = parent.getContext();
}
// 返回外层 分组的 item
View view = LayoutInflater.from(context).inflate(R.layout.image_group_item, parent, false);
final ViewHolder holder = new ViewHolder(view);
return holder;
}
//将数据绑定到控件上
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
PhotoGroup PhotoGroup = photoGroupList.get(position);
ChildAdapter childAdapter = (ChildAdapter) holder.recyclerView.getAdapter();
if (childAdapter == null){
// 设定 image_group_item 布局(每列显示4个子布局 image_child_item )
GridLayoutManager manager = new GridLayoutManager(context, 4);
holder.recyclerView.setLayoutManager(manager);
// 设置子布局的适配器 【PhotoGroup.getPhotoArrayList() 是一个分组中的图片数据】
holder.recyclerView.setAdapter(new ChildAdapter(PhotoGroup.getPhotoArrayList()));
}else {
// 重用子布局的适配,如果已经初始化过,直接调用 notifyDataSetChanged() 刷新布局就行
childAdapter.setData(PhotoGroup.getPhotoArrayList());
childAdapter.notifyDataSetChanged();
}
}
@Override
public int getItemCount() {
return photoGroupList.size();
}
// ViewHolder 对应 image_group_item 文件,里面元素只有最外层 CardView 和一个嵌套 RecyclerView
static class ViewHolder extends RecyclerView.ViewHolder{
CardView cardView;
RecyclerView recyclerView;
public ViewHolder(View view){
super(view);
cardView = (CardView)view;
recyclerView = (RecyclerView) view.findViewById(R.id.group_item_recyclerView);
}
}
/*****************************ChildAdapter ***************************************/
// 嵌套子适配(内部类)
public class ChildAdapter extends RecyclerView.Adapter<ChildAdapter.ChildViewHolder>{
// 每一个组的图片数据
private ArrayList<Photo> photoList;
//构造函数
public ChildAdapter(ArrayList<Photo> photoList) {
this.photoList = photoList;
}
//用于更新图片数据
public void setData(ArrayList<Photo> photoList) {
this.photoList = photoList;
}
@NonNull
@Override
public ChildViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (context == null){
context = parent.getContext();
}
// 返回嵌套内层 显示图片的 item
View view = LayoutInflater.from(context).inflate(R.layout.image_child_item, parent, false);
final ChildViewHolder holder = new ChildViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(@NonNull ChildViewHolder holder, int position) {
Photo image = photoList.get(position);
// 使用 Glide 加载图片,image.getFilePath() 获取图片的物理地址, into(holder.imageView) 将图片显示到 holder.imageView 部件上
Glide.with(context).load(image.getFilePath()).into(holder.imageView);
}
@Override
public int getItemCount() {
return photoList.size();
}
//ChildViewHolder 对应 image_group_item 文件,里面元素只有最外层 CardView 和一个用于显示图片的 ImageView
class ChildViewHolder extends RecyclerView.ViewHolder{
CardView cardView;
ImageView imageView;
public ChildViewHolder(@NonNull View itemView) {
super(itemView);
cardView = (CardView)itemView;
imageView = (ImageView) itemView.findViewById(R.id.image_item_imageView);
}
}
}
}
看起来复杂,其实很多都是重复的动作,最后将主界面与适配绑定,在 MainActivity 文件中,当获得权限后会调用 initView() 方法进行获取数据,获取数据是异步的,当获取完成后会回掉 imageOnLoadFinished() 方法。前面已经拿到了手机相册中的所有图片,并存放在 fileInfo 数组中
MainActivity.java:
private void initView(){
//当数据获取完成后回掉方法
ImageVideoLoader.ImageFinishCallback imageFinishCallback = new ImageVideoLoader.ImageFinishCallback() {
@Override
public void imageOnLoadFinished() {
//最终 ImageVideoLoader 类获取到的数据
fileInfo = imageVideoLoader.getPhotoGroupList();
// 获取 main_activity 布局文件中的 RecyclerView 部件实例
RecyclerView recyclerView = (RecyclerView)findViewById(R.id.main_recyclerView);
//设置 RecyclerView 部件的布局,每行显示一列
GridLayoutManager layoutManager = new GridLayoutManager(this, 1);
recyclerView.setLayoutManager(layoutManager);
//实例化适配器
PhotoAdapter adapter = new PhotoAdapter(fileInfo);
recyclerView.setAdapter(adapter);
}
};
// 实例化
imageVideoLoader = new ImageVideoLoader(view.getContext(), imageFinishCallback);
// 获取图片数据
getLoaderManager().initLoader(0,null, imageVideoLoader);
}
这样就可以显示了,但是缺少分组标题
4.添加浮动标题栏
浮动标题栏实现方法有很多种,可以通过继承 Recyclerview 的 ItemDecoration 来自定义分割线来实现
public class StickyDecoration extends RecyclerView.ItemDecoration {
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
}
- getItemOffsets:通过Rect为每个Item设置偏移,用于绘制Decoration
- onDraw:通过该方法,在Canvas上绘制内容,在绘制Item之前调用。(如果没有通过getItemOffsets设置偏移的话,Item的内容会将其覆盖)
- onDrawOver:通过该方法,在Canvas上绘制内容,在Item之后调用。(画的内容会覆盖在item的上层)
通过重写 onDraw() 方法实先分隔条效果,通过 drawOver() 方法实现悬浮效果,因为它是绘制在 item 之上的,所以我们就可以控制decoration实现
public class SectionDecoration extends RecyclerView.ItemDecoration{
//画笔
private final Paint paint;
// 标题栏高度
private int mDecorationHeight = 150;
//回掉,用于获取图片分组标题数据
private OnTagListener listener;
//文字画笔
private final TextPaint mTextPaint;
//定义文字格式
private Paint.FontMetrics fontMetrics;
//构造方法
public SectionDecoration(OnTagListener listener) {
super();
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.parseColor("#FFFFFF"));//标题栏背景色
this.listener = listener;
mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(Color.DKGRAY);//文字颜色
mTextPaint.setTextSize(48);//文字大小
mTextPaint.setTextAlign(Paint.Align.LEFT);//设置文字对齐方式
fontMetrics = mTextPaint.getFontMetrics();
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
//为每个decoration预留空间
outRect.top = mDecorationHeight;
}
// 会在绘制item之前进行绘制 如果没有在getItemOffsets中设置偏移的话,会被item覆盖
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
int childCount = parent.getChildCount();
int left = parent.getLeft();
int right = parent.getRight();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
int decorationBottom = child.getTop(); //item的top 也就是decoration的bottom
int top = decorationBottom - mDecorationHeight; //计算出decoration的top
c.drawRect(left,top,right,decorationBottom,paint); //绘制
}
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = state.getItemCount();//获取所有item的数量
int childCount = parent.getChildCount();//获取当前屏幕显示的item数量
int left = parent.getLeft();
int right = parent.getRight();
String preTag;
String curTag = null;
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(child);//获取在列表中的位置
preTag = curTag;
curTag = listener.getTag(position);//获取当前位置tag
if (curTag==null|| TextUtils.equals(preTag,curTag)) //如果两个item属于同一个tag,就不绘制
continue;
int bottom = child.getBottom(); //获取item 的bottom
float tagBottom = Math.max(mDecorationHeight,child.getTop());//计算出tag的bottom
if (position+1<itemCount){ //判断是否是最后一个
String nextTag = listener.getTag(position + 1); //获取下个tag
if (!TextUtils.equals(curTag,nextTag)&&bottom<tagBottom){ //被顶起来的条件 当前tag与下个tag不等且item的bottom已小于分割线高度
tagBottom = bottom; //将 item 的 bottom 值赋给 tagBottom 就会实现被顶上去的效果
}
}
c.drawRect(left,tagBottom-mDecorationHeight,right,tagBottom,paint); //绘制tag文字
float distance = (fontMetrics.bottom - fontMetrics.top)/2 - fontMetrics.bottom;
float baseline = (tagBottom-mDecorationHeight/2)+distance;
c.drawText(curTag,50,baseline,mTextPaint); //将tag绘制出来
}
}
//回掉接口
public interface OnTagListener{
String getTag(int position); //为了获取当前tag
}
}
注意:
drawText(text, x, y, paint) 方法将文字绘制出来,第一个 text 参数是要绘制的文字,第二第三 x 和 y 参数是要绘制的位置坐标,第四个参数是所用的画笔
注意 x 和 y 参数并不是指定文字的中点,并且 x, y 与文字对齐方式有关(通过设置画笔的 setTextAlign() 方法指定,默认为left)
y 对应的横线并不是文字的下边界,而是基准线Baseline
通过这些来获取这些线的 y 的坐标值
Paint.FontMetrics fontMetrics=paint.getFontMetrics();
fontMetrics.top
fontMetrics.ascent
fontMetrics.descent
fontMetrics.bottom
所以文字的中心点和,y 坐标值(基线)是有一个偏移量的,所以设置的 y 值应该是 中心点 y 值坐标+偏移量
//偏移量
float distance = (fontMetrics.bottom - fontMetrics.top)/2 - fontMetrics.bottom;
//实际的y值
float baseline = (tagBottom-mDecorationHeight/2)+distance;
最后将自定义分割线设置到 RecyclerView 中
private void initView(){
RecyclerView recyclerView = (RecyclerView)findViewById(R.id.main_recyclerView);
GridLayoutManager layoutManager = new GridLayoutManager(this, 1);
recyclerView.setLayoutManager(layoutManager);
/**********************将自定义分割线设置到 RecyclerView 中**************************/
recyclerView.addItemDecoration(new SectionDecoration(new SectionDecoration.OnTagListener(){
@Override
public String getTag(int position) {
if(fileInfo.get(position).getTitle()!=null) {
return fileInfo.get(position).getTitle();
}
return "-1";
}
}));
/******************************************/
adapter = new PhotoAdapter(fileInfo);
recyclerView.setAdapter(adapter);
}
至此已实现我们想要的效果
使用 LiveData+ViewModel
上面使用 getLoaderManager().initLoader(0,null, imageVideoLoader); 方法会标记已经过时,官方推荐使用 LiveData+ViewModel 替代
首先添加依赖
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
官方文档:https://developer.android.google.cn/topic/libraries/architecture/viewmodel
LiveData的优点:不用手动控制生命周期,不用担心内存泄露,数据变化时会收到通知。
ViewModel的优点:不用持有任何View对象不需要绑定View。为Activity 、Fragment存储数据,直到完全销毁。尤其是屏幕旋转的场景,常用的方法都是通过onSaveInstanceState()保存数据,再在onCreate()中恢复,很麻烦。
public class PhotoViewModel extends ViewModel {
private MutableLiveData<ArrayList<PhotoGroup>> photoLiveData;
String[] STORE_IMAGES = {
MediaStore.Images.Media.DATA,
MediaStore.Images.Media.DATE_ADDED,
MediaStore.Images.Thumbnails.DATA
};
//获得 LiveData 对象
public LiveData<ArrayList<PhotoGroup>> getPhotoLiveData() {
if (photoLiveData == null) {
photoLiveData = new MutableLiveData<ArrayList<PhotoGroup>>();
loadPhoto();
}
return photoLiveData;
}
// 加载图片数据
private void loadPhoto(){
new Thread(new Runnable() {
@Override
public void run() {
ArrayList<PhotoGroup> photoGroupList = new ArrayList<PhotoGroup>();
Cursor cursor = MyApplication.getContext().getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
STORE_IMAGES,
MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?",
new String[]{"image/jpeg", "image/png"},
MediaStore.Images.Media.DATE_MODIFIED+" desc");
if(cursor.moveToNext()){
int thumbPathIndex = cursor.getColumnIndex(MediaStore.Images.Thumbnails.DATA);
int timeIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED);
int pathIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
long lastDate = cursor.getLong(timeIndex);//获得第一个时间
ArrayList<Photo> photoList = new ArrayList<>();
do {
String thumbPath = cursor.getString(thumbPathIndex);
long date = cursor.getLong(timeIndex);
String filepath = cursor.getString(pathIndex);
if (!getDate(lastDate).equals(getDate(date))){
lastDate = date;
PhotoGroup photoGroup = new PhotoGroup();
photoGroup.setTitle(photoList.get(0).getDate());
photoGroup.setPhotoArrayList(photoList);
photoGroupList.add(photoGroup);
photoList = new ArrayList<>();
}
Photo photo = new Photo();
File f = new File(filepath);
photo.setTime(date);
photo.setThumbPath(thumbPath);
photo.setFilePath(filepath);
photo.setFileName(f.getName());
photoList.add(photo);
}while (cursor.moveToNext());
PhotoGroup photoGroup = new PhotoGroup();
photoGroup.setTitle(photoList.get(0).getDate());
photoGroup.setPhotoArrayList(photoList);
photoGroupList.add(photoGroup);
}
//主线程使用 photoLiveData.setValue()方法,子线程使用 photoLiveData.postValue() 方法
//setValue()只能在主线程中调用,postValue() 可以在任何线程中调用
photoLiveData.postValue(photoGroupList);
cursor.close();
}
}).start();
}
@Override
protected void onCleared() {
super.onCleared();
// 释放资源
}
private String getDate(long time) {
return new SimpleDateFormat("yyyy年MM月dd日")
.format(new Date(time*1000L));
}
}
在 PhotoViewModel 中创建了 MutableLiveData(MutableLiveData 是 LiveData 的子类)的实例,里面存放了系统相册图片的数据,通过PhotoViewModel 中的 getPhotoLiveData 方法得到。
loadPhoto() 用来加载系统相册图片,与上面相同,没什么好说的, photoLiveData.postValue() 方法将图片数据设置到 LiveData 对象中,当 LiveData 中数据发生变化时,会有监听器监听到并进行 UI 的更新等操作,注意 photoLiveData.postValue() 代码中的注释
/****注意省略权限获取操作****/
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private PhotoAdapter adapter;// 适配器
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final PhotoViewModel model = ViewModelProviders.of(this).get(PhotoViewModel.class);
//监听 LiveData 中 photoLiveData 属性变化,只要触发了setValue/postValue方法就会被调用
model.getPhotoLiveData().observe(this, new Observer<ArrayList<PhotoGroup>>() {
@Override
public void onChanged(final ArrayList<PhotoGroup> photoGroups) {
recyclerView = (RecyclerView)findViewById(R.id.main_recyclerView);
GridLayoutManager layoutManager = new GridLayoutManager(MainActivity.this, 1);
recyclerView.setLayoutManager(layoutManager);
recyclerView.addItemDecoration(new SectionNewDecoration(new SectionNewDecoration.OnTagListener(){
@Override
public String getTag(int position) {
if(photoGroups.get(position).getTitle()!=null) {
return photoGroups.get(position).getTitle();
}
return "-1";
}
}));
adapter = new PhotoAdapter(photoGroups);
recyclerView.setAdapter(adapter);
}
});
}
}
创建Observer对象,调用 observe() 方法,添加观察者,当数据变化时会通过回调方法通知观察者。
最后实现效果与上面相同
未完待续