Android图片编辑器实践指南-2.图片视频选择器(2)

本文出自博客Scott_S CSDN博客,如需转载请标明出处,尊重原创谢谢
博客地址: https://blog.csdn.net/Scott_S/article/details/84186144
github地址: https://github.com/sunquan9301/MediaEditor

图片选择

上一篇已经介绍完查询系统图片的核心函数,如果不清楚,可参看上篇文章,本篇将完成图片选择器的开发。先上效果图:
在这里插入图片描述

新建ChooseMediaActivity,并在AndroidManifest.xml中进行注册,在布局文件activity_choose_media.xml布局文件中,添加如下代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:orientation="vertical">

    <com.sunquan.mediaeditor.view.TitleBar
        android:id="@+id/titlebar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:s_title="choose media" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycleView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

RecycleView,在build.gradle已经添加了相关dependency,Titlebar是自己定义的拥有特定属性的公共控件。 这里附带说明如何进行自定义view。

——————————————分割线——————————————

自定义view

先构思该view需要提供哪些功能,可以拿笔清晰明了的列下来,然后从所列出的功能里圈出互斥的功能,即可变的功能,针对这些可变的功能,定义对应的字段来控制该view的行为。

具体到该案例
需要的功能:

  • 左边返回back键,常态,不需要字段控制
  • 中间显示的标题,颜色,大小常态,内容可变,需要一个属性控制显示的内容。
  • 右面确定按钮,颜色,大小,样式常态,内容可变,需要属性控制显示的内容。

定义属性字段:

    <declare-styleable name="TitleBar">
        <attr name="s_title" format="string|reference" />
        <attr name="s_right" format="string|reference" />
    </declare-styleable>
    

s_title 控制显示标题的内容
s_right 控制确定按钮的标题内容
format标志字段的输入格式,这里可以直接输入字符串,也可以输入r.string.里面的引用。

因此定义一个TitleBar类,在布局文件中定义左边back按钮,中间TextView标题,和右边TextView按钮,具体步骤如下:

  1. inflate布局文件
  2. 获取初始化属性字段
  3. 绑定view的交互事件
  4. 提供接口暴露功能给外部调用

具体代码如下:

//初始化
public TitleBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
   super(context, attrs, defStyleAttr);
   this.context = context;
   inflate(context, R.layout.view_titlebar, this);
   if (attrs != null) {
        getAttrs(context, attrs);
   }
}

//获取属性
private void getAttrs(Context context, AttributeSet attrs) {
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TitleBar);
    titleStr = ta.getString(R.styleable.TitleBar_s_title);
    right = ta.getString(R.styleable.TitleBar_s_right);
    initView();
    bindAction();
}

//定义接口
private OnTitleBarClickListener listener;
public abstract static class OnTitleBarClickListener {
    public void clickBack(){
    }
    public void clickConfirm(){
    }
}

//绑定事件
private void bindAction() {
    back.setOnClickListener(v -> {
         if (listener != null) {
            listener.clickBack();
         }
    });
    confirm.setOnClickListener(v -> {
         if (listener != null) {
            listener.clickConfirm();
         }
    });
}

//在别的布局文件中使用TitleBar
<com.sunquan.mediaeditor.view.TitleBar
    android:id="@+id/titlebar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:s_title="choose media" />

——————————————分割线——————————————

说完了自定义View相关内容,返回主线,我们设置RecycleView,并为其绑定Adapter。

//com.sunquan.mediaeditor.component.MediaChooseComponent.java
adapter = new MediaChooseAdapter(this);
recyclerView.setLayoutManager(new GridLayoutManager(this
	, ChooseMediaActivity.GRID_COLUMS));
recyclerView.addItemDecoration(new MediaChooseItemDecoration(
	ChooseMediaActivity.GRID_COLUMS, ViewUtil.dp2px(this, 1), false));
recyclerView.setAdapter(adapter);
((SimpleItemAnimator)recyclerView.getItemAnimator())
	.setSupportsChangeAnimations(false);
adapter.loadData("");
  • 首先新建一个MediaChooseAdapter
  • 设置LayoutManager为GridLayoutManager,并标明具体几列ChooseMediaActivity.GRID_COLUMS
  • 设置ItemDecoration标明每列之间的间隔dp2px方法如下:
//com.sunquan.mediaeditor.utils.ViewUtil
public static int dp2px(Context context, float dp) {
    final float scale = context.getResources().getDisplayMetrics().density;
    return (int) (dp * scale + 0.5f);
}
  • 绑定adapter,禁止recycleview的默认动画
  • loadData

loadData具体所做的事情是访问MediaChooseComponent.java并获取其查询的Medias,接下来转到MediaChooseComponent.java中的loadImageMedias方法。在次之前,定义了如下2个属性,一个线程和一个list。

    private Thread imageQueryThread;
    private List<MediaItem> imageMediasCache = new ArrayList<>();

在loadImageMedias方法中,主要做如下几件事情

  • 判断imageQueryThread是否还在运行,如果还在运行,则中断它
if (imageQueryThread != null && imageQueryThread.isAlive()) {
    imageQueryThread.interrupt();
    imageQueryThread = null;
}  
  • 遍历缓存,并调用listener.notifyMedia()方法通知外部
for (MediaItem mediaInfo : imageMediasCache) {
     if (listener != null) {
         listener.notifyMedia(mediaInfo);
     }
}
  • 发起queryImageList Task
imageQueryThread = new Thread(() -> {
     queryImageList(dir);
});
imageQueryThread.start();
  • 具体queryImageList()方法可查看上一篇文章,这里稍作修改,针对查询出来的每一个MediaInfo,我们首先加入到cache list中,同时调用list.notifyMedia通知订阅方。

接下来转到MediaChooseAdapter.java类中,我们需要实现如下三个方法

//com.sunquan.mediaeditor.photo.choose.MediaChooseAdapter.java
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {}

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {}

    @Override
    public int getItemCount() {}

实现之前,构造函数中添加如下代码

	//com.sunquan.mediaeditor.photo.choose.MediaChooseAdapter.java
    public MediaChooseAdapter(Context context) {
        this.context = context;
        this.mItemSize = (ViewUtil.getScreenWidth(context)(ChooseMediaActivity.GRID_COLUMS - 1) 
        	* ViewUtil.dp2px(context, 1)) / ChooseMediaActivity.GRID_COLUMS;
        ME.media(context).setOnMediaChangeListener(mediaInfo -> {
            if (!isExist(mediaInfo)) {
                ((Activity) context).runOnUiThread(() -> {
                    items.add(mediaInfo);
                    notifyItemInserted(items.size() - 1);
                });
            }
        });
    }
    //com.sunquan.mediaeditor.utils.ViewUtil.java
    public static int getScreenWidth(Context context) {
            WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            Display d = wm.getDefaultDisplay();
            DisplayMetrics realDisplayMetrics = new DisplayMetrics();
            d.getRealMetrics(realDisplayMetrics);
            return realDisplayMetrics.widthPixels;
    }

首先定义了一个字段mItemSize用来标志图片显示的大小,其次订阅MediaChooseComponent.java中的MediaChange接口。针对每个MediaInfo,首先判断是否已经存在在集合中,不存在则添加进去并调用notifyItemInserted()刷新界面。由于查询Media在异步线程运行,需要调用runOnUiThread方法使之在UI线程更新界面。

//com.sunquan.mediaeditor.photo.choose.MediaChooseAdapter.java
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new MediaVH(parent);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof MediaVH) {
            ((MediaVH) holder).bind(items.get(position), position, (MediaVH) holder);
        }
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

接下来,在onCreateViewHolder方法里,new了一个内部类,用于初始化view显示每一个图片,在onBindViewHolder中进行数据绑定,getItemCount方法返回size就。内部类就不细说了,主要贴一下用Fresco load image的方法

	//com.sunquan.mediaeditor.utils.FrescoUtil.java
    public static void loadImage(SimpleDraweeView simpleDraweeView, String url) {
        if (TextUtils.isEmpty(url)) {
            return;
        }

        Uri uri = Uri.fromFile(new File(url));
        ImageRequest request = ImageRequestBuilder
                .newBuilderWithSource(uri)
                .build();
        PipelineDraweeController controller = (PipelineDraweeController) Fresco.newDraweeControllerBuilder()
                .setOldController(simpleDraweeView.getController())
                .setImageRequest(request)
                .setAutoPlayAnimations(true)
                .build();
        simpleDraweeView.setController(controller);
    }

最后我们在adapter中loadData方法中发起查询Media的Task就可以看到效果了。

	//com.sunquan.mediaeditor.photo.choose.MediaChooseAdapter.java
    public void loadData(String dir) {
        items.clear();
        notifyDataSetChanged();
        ME.media(context).loadImageMedias(dir);
    }

根据目录显示图片

接下来我们为其添加按目录显示图片功能,先上图:
在这里插入图片描述

在MediaChooseComponent.java类中添加如下代码:

	//com.sunquan.mediaeditor.component.MediaChooseComponent.java
    private Set<String> imageDirs = new HashSet<>();
    private HashMap<String, List<MediaItem>> dirMapImage = new HashMap<>();
  • imageDirs用于存储所有都目录
  • dirMapImage 用于存储对应目录下的图片
    接着对queryImageList函数进行修改,添加dir参数,在查询到每一个MediaInfo做如下处理:
	//com.sunquan.mediaeditor.component.MediaChooseComponent.java
String filePath = imageCursor.getString(col_data_image);
if (TextUtils.isEmpty(filePath)) {
    continue;
}
final String mediaDir = getMediaDir(filePath);
//dir cache
imageDirs.add(mediaDir);
//通知dir更新
if (dirListener != null) {
    dirListener.notifyMediaDir(mediaDir);
}
//传入到dir和mediaInfo的dir不匹配则跳过
if (!TextUtils.isEmpty(dir) && !isMatch(getMediaDir(filePath), dir)) {
    continue;
}

MediaItem imageInfo = ...
//加入dirMap
if (dirMapImage.get(imageInfo.dir) == null) {
    List<MediaItem> medias = new ArrayList<>();
    medias.add(imageInfo);
    dirMapImage.put(imageInfo.dir, medias);
} else {
    final List<MediaItem> mediaInfos = dirMapImage.get(imageInfo.dir);
    if (!isExistInImageCache(imageInfo, mediaInfos)) {
       mediaInfos.add(imageInfo);
    }
}
//如果匹配dir则通知media
if (listener != null && isMatch(imageInfo.dir, dir)) {
    listener.notifyMedia(imageInfo);
}

新建ChooseMediaDirActivity.java 在AndroidManifest.xml文件中注册,除了ChooseMediaActivity.java的代码外,在其布局文件中额外加入一个TextView用于显示目录title

     <TextView
        android:id="@+id/dir"
        android:layout_width="match_parent"
        android:layout_height="44dp"
        android:gravity="center"
        android:textColor="@android:color/black"
        android:textSize="20sp"
        tools:text="全部照片" />

在ChooseMediaDirActivity.java添加PopupWindow用于显示目录选项

//com.sunquan.mediaeditor.photo.choose.ChooseMediaWithDirActivity.java
    private MediaChooseAdapter adapter;
    private MediaDirAdapter dirAdapter;
    private PopupWindow popupWindow;
    private boolean isShowDir;

        RecyclerView rvDir = (RecyclerView) View.inflate(this, R.layout.recycleview, null);
        rvDir.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));

        dirAdapter = new MediaDirAdapter(this);
        rvDir.setAdapter(dirAdapter);
        dirAdapter.setData(ME.media(this).getDirs());

        popupWindow = new PopupWindow(rvDir,
                WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
        popupWindow.setBackgroundDrawable(new ColorDrawable(
                this.getResources().getColor(android.R.color.white)));
        popupWindow.setOutsideTouchable(true);

new了一个RecycleView绑定在PopupWindow上,通过对应的dirAdapter提供目录数据,目录数据直接调用MediaChooseComponent.java类的getDirs()方法获得。在点击目录title的时候弹出目录列表,点击其中一个目录列表调用adapter.loadData(dir) load数据。如此即可实现根据目录显示图片列表。

总结
此篇实现了图片选择器,并扩展了根据目录进行浏览图片的功能,于此同时详细说明了自定义view的步骤。下一篇将进行图片浏览功能的实现。

更多精彩技术分享请扫码关注小码时间(XiaoMaTime)

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值