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

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

先上效果图:

初始界面

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

<?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:fitsSystemWindows="true"
    android:orientation="vertical">

    <include layout="@layout/layout_toolbar" />

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <EditText
                android:id="@+id/id_input_content_text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                android:gravity="start"
                android:hint="这一刻你想说的..."
                android:lineSpacingExtra="5dp"
                android:minLines="5"
                android:padding="@dimen/activity_margin"
                android:textColor="@color/colorPrimaryDark"
                android:textSize="14sp" />

            <View
                android:layout_width="match_parent"
                android:layout_height="0.1dp"
                android:background="@color/divider" />

            <android.support.v7.widget.RecyclerView
                android:id="@+id/id_image_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                android:nestedScrollingEnabled="false"
                android:overScrollMode="never"
                android:padding="8dp"
                android:scrollbars="none"
                app:layout_behavior="@string/appbar_scrolling_view_behavior" />

            <RelativeLayout
                android:id="@+id/id_show_location_check_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:background="@drawable/white_item"
                android:paddingBottom="8dp"
                android:paddingLeft="@dimen/activity_margin"
                android:paddingRight="@dimen/activity_margin"
                android:paddingTop="8dp">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerVertical="true"
                    android:text="显示位置信息"
                    android:textSize="16sp" />

                <CheckBox
                    android:id="@+id/id_location_check_button"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentRight="true"
                    android:layout_centerVertical="true"
                    android:clickable="false"
                    android:focusable="false"
                    android:focusableInTouchMode="false" />

            </RelativeLayout>
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>
    
</LinearLayout>

界面采用的是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<String> {

    public LocalImageGridAdapter(Context context, List<String> 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<T> extends RecyclerView.Adapter<AbsViewHolder> {

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

    public AbsRecyclerAdapter(Context context, List<T> 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<String> 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<String> getTempList() {
        ArrayList<String> temp = (ArrayList<String>) 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<String> 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 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,441评论 25 708
  • 内容抽屉菜单ListViewWebViewSwitchButton按钮点赞按钮进度条TabLayout图标下拉刷新...
    皇小弟阅读 46,898评论 22 665
  • 去年底便开始计划辞职后放飞自我,于是上网搜了不少购特价机票的攻略,可据我这几个月的观察,还是买的没有卖的精呐! 本...
    废宅小鱼儿阅读 207评论 0 2
  • 排骨萝卜汤、炒虾仁(配杂菜)芦笋炒豆腐干、红扁豆烧芋艿、红烧鸡、蒸海蟹、荠菜蘑菇粉丝蛋汤(用啤酒当料酒味道不错)
    紫微妈咪阅读 205评论 0 0
  • ”微商,英文名称WeChat Business,它是企业或者个人基于社会化媒体开店的新型电商,从模式上来说主要分为...
    波妮塔阅读 525评论 0 1