笔记:Android 时间轴相册

在这里插入图片描述
总的来说分为两步: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() 方法,添加观察者,当数据变化时会通过回调方法通知观察者。
最后实现效果与上面相同

未完待续

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值