Android仿微信图片选择器(一)

最近在开发一个具有社交功能的App,其中有一个发表动态的功能(类似于微信朋友圈),在网上找了一些资料,结果并不能达到我想要的效果,所以决定自己动作撸一个出来。在开发此功能的过程中踩了不少坑,也得到不少的经验,特此在这里写博客记录一下。博客分三篇,第一篇是介绍发表界面的编写及图片回掉显示的功能实现,第二篇介绍图片选择功能的实现,第三篇介绍图片预览界面的实现。

先上效果图:

Android仿微信图片选择器(一)_第1张图片
初始界面

发表界面的编写比较简单,直接上代码:




    

    

        

            

            

            

            

                

                

            
        
    
    

界面采用的是NestedScrollView嵌套RecyclerView的方法,此处有一坑,两种可滚动的view嵌套的方式不用说肯定存在滑动冲突,或者是存在RecyclerView显示不完整的问题,通过查阅各种资料找到的解决方法是给RecyclerView添加属性android:nestedScrollingEnabled="false",即RecyclerView的滚动处理交由父View即NestedScrollView进行处理,此属性仅在api21以上有效,然后再次通过查阅各种资料发现,添加app:layout_behavior="@string/appbar_scrolling_view_behavior"亦可达到这种效果,具体原因与layout_behavior有关,由于时间关系没有深入去了解layout_behavior,先暂且如此使用。

接下来是java代码的编写,有RecyclerView必然少不了RecyclerView.Adapter, 先看代码的实现:

public class LocalImageGridAdapter extends AbsRecyclerAdapter {

    public LocalImageGridAdapter(Context context, List list) {
        super(context, list);
    }

    @Override
    protected AbsViewHolder createHolder(ViewGroup parent, int viewType) {
        return new ImageHolder(mInflater.inflate(R.layout.layout_image, parent, false));
    }

    @Override
    protected void showViewHolder(AbsViewHolder holder, final int position) {
        final ImageHolder imageHolder = (ImageHolder) holder;
        if (mData.get(position).startsWith("file:///")) {
            Picasso.with(mContext)
                    .load(mData.get(position))
                    .resize(DisplayUtil.dip2px(mContext, 72), DisplayUtil.dip2px(mContext, 72))
                    .centerCrop()
                    .config(Bitmap.Config.RGB_565)
                    .into(imageHolder.imageView);
        } else {
            Picasso.with(mContext)
                    .load(new File(mData.get(position)))
                    .resize(DisplayUtil.dip2px(mContext, 72), DisplayUtil.dip2px(mContext, 72))
                    .centerCrop()
                    .error(R.drawable.ic_load_error)
                    .placeholder(R.drawable.ic_place_holder)
                    .config(Bitmap.Config.RGB_565)
                    .into(imageHolder.imageView);
        }
    }

    private static class ImageHolder extends AbsViewHolder {

        ImageView imageView;

        ImageHolder(View itemView) {
            super(itemView);
            imageView = (ImageView) itemView.findViewById(R.id.id_image_view);
        }
    }
}

Picasso支持加载本地资源图片、assets图片和网络图片,用法基本略有不同,区别在于加载本地文件图片的时需要load(new File(path))。此处采用一种取巧的方式添加默认图片,即将默认图片放置在assets文件夹下,通过路径file:///android_asset/add_image.png加载即可保证Adapter的数据源统一为String类型。此前把资源文件放在res/drawable文件夹下,把数据源设置为Object类型,然后通过instanceof的方式判断采取何种方式加载图片,虽说可以实现同样的效果,但是在后期上传图片的时候多了一些操作,最后就舍弃这种做法。代码中AbsRecyclerAdapter是自己封装的抽象的Adapter,封装的内容很简单,仅包含对itemView的点击事件处理和数据的初始化,并不支持多种类型的item。具体封装如下:

public abstract class AbsRecyclerAdapter extends RecyclerView.Adapter {

    protected Context mContext;
    protected LayoutInflater mInflater;
    protected List mData = new LinkedList<>();
    private OnItemClickListener onItemClickListener;

    public AbsRecyclerAdapter(Context context, List list) {
        this.mContext = context;
        mInflater = LayoutInflater.from(context);
        if (list != null) {
            this.mData = list;
        }
    }

    @Override
    public AbsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return createHolder(parent, viewType);
    }

    @Override
    public void onBindViewHolder(final AbsViewHolder holder, int position) {
        showViewHolder(holder, position);
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (onItemClickListener != null) {
                    onItemClickListener.onClick(view, holder.getAdapterPosition());
                }
            }
        });
        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                if (onItemClickListener != null) {
                    return onItemClickListener.onLongClick(view, holder.getAdapterPosition());
                }
                return false;
            }
        });
    }

    protected abstract AbsViewHolder createHolder(ViewGroup parent, int viewType);

    protected abstract void showViewHolder(AbsViewHolder holder, int position);

    @Override
    public int getItemCount() {
        return mData != null ? mData.size() : 0;
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    public interface OnItemClickListener {

        void onClick(View view, int position);

        boolean onLongClick(View view, int position);

    }

    public static class DefaultItemClickListener implements OnItemClickListener {

        @Override
        public void onClick(View view, int position) {

        }

        @Override
        public boolean onLongClick(View view, int position) {
            return false;
        }
    }

最后是Activity类的实现,仅贴核心代码:

    private RecyclerView mImageGridView;
    private List mImagePath;
    private LocalImageGridAdapter mImageGridAdapter;
    private static final int PHOTO_REQUEST_CODE = 100;
    private static final String ADD_IMAGE_PATH = "file:///android_asset/add_image.png";
    private static final int DEAFULT_SELECTED_COUNT = 9;

    private void initImageGridView() {
        mImagePath = new ArrayList<>(DEAFULT_SELECTED_COUNT);
        mImagePath.add(ADD_IMAGE_PATH);
        mImageGridAdapter = new LocalImageGridAdapter(this, mImagePath);
        mImageGridView.setLayoutManager(new GridLayoutManager(this, 4));
        mImageGridView.setItemAnimator(new DefaultItemAnimator());
        mImageGridView.setAdapter(mImageGridAdapter);
        mImageGridAdapter.setOnItemClickListener(new AbsRecyclerAdapter.OnItemClickListener() {
            @Override
            public void onClick(View view, int position) {
                if (position == mImagePath.size() - 1 && mImagePath.get(position).equals(ADD_IMAGE_PATH)) {
                    PhotoPickActivity.startActivityForResult(PublishActivity.this, PHOTO_REQUEST_CODE, RESULT_OK, getSelectedCount());
                } else {
                    PhotoPreviewActivity.startActivity(PublishActivity.this, getTempList(), position);
                }
            }

            @Override
            public boolean onLongClick(View view, int position) {
                if (position == mImagePath.size() - 1 && mImagePath.get(position).equals(ADD_IMAGE_PATH)) {
                    return false;
                }
                mImagePath.remove(position);
                mImageGridAdapter.notifyItemRemoved(position);
                checkSelectedCount();
                return true;
            }
        });
    }

    private int getSelectedCount() {
        return DEAFULT_SELECTED_COUNT - mImagePath.size() + 1;
    }

    private void checkSelectedCount() {
        if (mImagePath.size() >= DEAFULT_SELECTED_COUNT + 1) {
            mImagePath.remove(mImagePath.size() - 1);
            mImageGridAdapter.notifyItemRemoved(mImagePath.size() - 1);
        } else {
            if (mImagePath.get(mImagePath.size() - 1).equals(ADD_IMAGE_PATH)) {
                return;
            }
            mImagePath.add(ADD_IMAGE_PATH);
            mImageGridAdapter.notifyDataSetChanged();
        }
    }

    private ArrayList getTempList() {
        ArrayList temp = (ArrayList) mImagePath;
        if (temp.get(mImagePath.size() - 1).equals(ADD_IMAGE_PATH)) {
            temp.remove(mImagePath.size() - 1);
        }
        return temp;
    }

   @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            switch (requestCode) {
                case PHOTO_REQUEST_CODE:
                    if (data != null) {
                        ArrayList images = data.getStringArrayListExtra("data");
                        if (images != null && images.size() > 0) {
                            mImagePath.addAll(mImagePath.size() - 1, images);
                            mImageGridAdapter.notifyDataSetChanged();
                            checkSelectedCount();
                        }
                    }
                    break;
            }
        }
    }

RecyclerView的基本使用就不多介绍了,代码里有两个类比较重要,

  • PhotoPickActivity:图片选择
  • PhotoPreviewActivity:图片预览

当点击最后一张图也就是那张默认的添加图片,会进入到图片选择界面,PhotoPickActivity通过startActivityForResult的方式启动,需要一个请求码。点击其他图片时会进入图片预览界面,需要传入当前图片的数据和当前点击的图片的位置。长按的功能是移除不需要的图片。在点击事件处理过程中有两个方法,其中:

  • getSelectedCount()参数表示可选择图片数量,因添加了一张默认的图片,所以在计算的时候需要加上一张。
  • checkSelectedCount()作用是检查图片数量是否大于默认可选的图片数量,若达到最大的默认数量,则把默认的图片去除,否则就需要加上默认的图片。
  • getTempList()作用是为了去除最后一张默认图片。

onActivityResult处理的是图片选择返回的结果,通过data.getStringArrayListExtra("data")的方法获得返回的数据,并把数据添加到list里并更新界面,同时需要检查添加图片后图片的数量是否符合条件。

PhotoPickActivityPhotoPreviewActivity的具体实现将在后面的博客介绍,来看一下程序运行的结果图:

Android仿微信图片选择器(一)_第2张图片
选择图片后界面
  • Android仿微信图片选择器(二)
  • Android仿微信图片选择器(三)

你可能感兴趣的:(Android仿微信图片选择器(一))