仿微信朋友圈 9 图上传选择器

2,517 阅读6分钟

这个开源项目,之前就想写,一直没有时间整理,这次整理出来,方便以后使用,封装成了库,支持定制,废话不多说,先上图

这里写图片描述

这是微信的朋友圈发布选择器,一般大家都是用recyclerview或者gridview写一个出来,然后里面再做其他处理,当时我就想,我能不能把它封装成一个控件,然后以后就再也不用写了,得出的结论是能,于是开始封装,封装完成呢,效果图就是下面这个

这里写图片描述

动图太大上传不了了,那就放出github地址来

单纯的上传图片展示控件ImageShowPicker

这里的readme有动态图,大家可以看下。

废话不多说,大家先来看看怎么使用这个控件

 ImageShowPickerView  pickerView = (ImageShowPickerView)findViewById(R.id.it_picker_view);
        final List<ImageBean> list = getItem(position);
        pickerView.setImageLoaderInterface(new Loader());
        pickerView.setNewData(list);
        //展示有动画和无动画

         //设置监听
        pickerView.setPickerListener(new ImageShowPickerListener() {
            @Override
            public void addOnClickListener(int remainNum) {
                Toast.makeText(context, "remainNum" + remainNum, Toast.LENGTH_SHORT).show();
                //在listview或recyclerview才会使用这个list.add(),其他情况都不用
                list.add(new ImageBean("http://pic78.huitu.com/res/20160604/1029007_20160604114552332126_1.jpg"));
                pickerView.addData(new ImageBean("http://pic78.huitu.com/res/20160604/1029007_20160604114552332126_1.jpg"));
            }

            @Override
            public void picOnClickListener(List<ImageShowPickerBean> list, int position, int remainNum) {
                Toast.makeText(context, list.size() + "========" + position + "remainNum" + remainNum, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void delOnClickListener(int position, int remainNum) {
                list.remove(position);
                Toast.makeText(context, "delOnClickListenerremainNum" + remainNum, Toast.LENGTH_SHORT).show();
            }
        });
        //所有设置完毕后调用该方法
        pickerView.show();


        //获取所有数据
        pickerView.getDataList();

声明控件后做一些设置就使用这个控件了,是不是比之前的自己写recyclerview简单多了,再也不用一次次重写了,
这里就不做过多说明,更详细的使用方法,请大家移步github,欢迎star呀

下面,来为大家做一下介绍,看看我们的这个picker到底是怎么封装的,我是通过这几部来封装的

  • 1、分析需求,分析定制目标
  • 2、选择实现方法
  • 3、编写代码
  • 4、完善方法
    以上4步就是我这个项目的经历过程,下面一一介绍,并说明原理

1、分析需求,分析定制目标

我们要写的这个选择器,首先他要轻便,就是不抢其他人的工作,不去给开发者带来其他的局限性,so我们就不能
直接指定某个图片加载框架,者就是一个需求;其次,我们的选择器应该有多种定制选择,比如微信的选择器就没
有删除小按钮,但是其他的app里有,我们就要定制这一个属性,让用户来选择是否显示,还有recyclerview自
带的增加删除动画,有的app就想要,有的就不想要,so我们也要提供方法让使用者来定制。

2、选择实现方法

实现方法的选择是个问题,关于图片加载的实现方法,我参考了[banner]这个开源项目的接口处理,十分感谢该
项目给我的思路,他的地址如下https://github.com/youth5201314/banner,解决了这个问题,还有一个
问题困扰我,就是如何获取数据,开始我想用反射来进行操作,但是反射的性能不好,同时使用者混淆代码时,又
增添了很多麻烦,因为数据类的参数的不确定性,所以抛弃了这种处理方式,最后选择继承重写方法的这个思路来
实现。

3、编写代码

解决了上面两个问题,下面就可以开始写代码了,一点点来说

(1).为view 设置属性

这里一定要注意,自定义控件设置属性后一定要调用typedArray.recycle();这个方法,否则后果你可以试试,爽到飞起

  TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ImageShowPickerView);
        mPicSize = typedArray.getDimensionPixelSize(R.styleable.ImageShowPickerView_pic_size, SizeUtils.getSizeUtils().dp2px(getContext(), PIC_SIZE));
        isShowDel = typedArray.getBoolean(R.styleable.ImageShowPickerView_is_show_del, true);
        isShowAnim = typedArray.getBoolean(R.styleable.ImageShowPickerView_is_show_anim, false);
        mAddLabel = typedArray.getResourceId(R.styleable.ImageShowPickerView_add_label, R.mipmap.image_show_piceker_add);
        mDelLabel = typedArray.getResourceId(R.styleable.ImageShowPickerView_del_label, R.mipmap.image_show_piceker_del);
        oneLineShowNum = typedArray.getInt(R.styleable.ImageShowPickerView_one_line_show_num, ONE_LINE_SHOW_NUM);
        maxNum = typedArray.getInt(R.styleable.ImageShowPickerView_max_num, MAX_NUM);
        typedArray.recycle();

(2).添加数据时刷新,这里就包括动画的处理了

这只是其中一个方法,写到这里就是为了让大家了解一下,提供个思路

 /**
     * 添加新数据
     *
     * @param bean
     * @param <T>
     */
    public <T extends ImageShowPickerBean> void addData(T bean) {
        if (bean == null) {
            return;
        }
        this.list.add(bean);
        if (isShowAnim) {
            if (adapter != null) {
                adapter.notifyItemChanged(list.size() - 1);
                adapter.notifyItemChanged(list.size());
            }
        } else {
            adapter.notifyDataSetChanged();
        }

    }

(3).重头戏来了,也就是加载图片接口

我们定义这个接口,用来实现加载图片的自定义化,无论你用什么框架,就都可以兼容了,第一眼看,有的人可能没明白怎么处理,其实就是继承接口后重写这个方法,我们在view里只不过把这个方法当成已经写好的方法,调用了一下他的方法名,这样解释是不是就更清晰些,动手试试就懂了

/**
 * Author 姚智胜
 * Version V1.0版本
 * Description: 加载图片接口
 * Date: 2017/4/6
 */
public interface ImageLoaderInterface<T extends View> extends Serializable {

    void displayImage(Context context, String path, T imageView);

    void displayImage(Context context, @DrawableRes Integer resId, T imageView);

    T createImageView(Context context);
}

(4).RecyclerView的holder处理

把这个放到这里,主要是为了给大家看下onclick事件应该写在哪里,如果写到onBindViewHolder这个方法里,那就是无限的重复创建了,没有理解Holder的作用,我们要在最开始就对他进行创建,然后去复用他

  //自定义的ViewHolder,持有每个Item的的所有界面元素
    public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        public View iv_pic;
        public ImageView iv_del;
        private ImageShowPickerPicListener picOnClickListener;


        public ViewHolder(View view, ImageLoaderInterface imageLoaderInterface, ImageShowPickerPicListener picOnClickListener) {
            super(view);
            this.picOnClickListener = picOnClickListener;
            iv_pic = imageLoaderInterface.createImageView(view.getContext());
            FrameLayout.LayoutParams pic_params = new FrameLayout.LayoutParams(iconHeight,
                    iconHeight);
            pic_params.setMargins(10, 10, 10, 10);
            iv_pic.setLayoutParams(pic_params);
            iv_del = new ImageView(view.getContext());
            FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
            layoutParams.gravity = Gravity.TOP | Gravity.END;
            iv_del.setPadding(5, 5, 5, 5);
            iv_del.setLayoutParams(layoutParams);
            iv_pic.setId(R.id.iv_image_show_picker_pic);
            iv_del.setId(R.id.iv_image_show_picker_del);
            iv_pic.setOnClickListener(this);
            iv_del.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            int i = v.getId();
            if (i == R.id.iv_image_show_picker_pic) {
                picOnClickListener.onPicClickListener(getLayoutPosition());
            } else if (i == R.id.iv_image_show_picker_del) {
                picOnClickListener.onDelClickListener(getLayoutPosition());
            }
        }
    }

(5).意外发现recyclerview的bug

在代码里,我们在adapter内部使用了notifyItemChanged,程序却异常崩溃了,查阅过资料后,发现这个google的bug,所以重写了这个布局管理器,处理这个问题,算是意外收获

/**
 * Author 姚智胜
 * Version V1.0版本
 * Description: 处理recyclerview在adapter内调用notifyItemChanged崩溃的解决方法
 * Date: 2017/4/15
 */

public class MyGridLayoutManager extends GridLayoutManager {
    public MyGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public MyGridLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
    }

    public MyGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
    }

    @Override
    public boolean supportsPredictiveItemAnimations() {
        return false;
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        //override this method and implement code as below
        try {
            super.onLayoutChildren(recycler, state);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

(6).数据类的父类

原理很简单,只是思路问题,我们继承这个父类后,只需重写两个方法,把我们数据类的url,为指定方法赋值就可以了,一个小思路,有可能不是最好的,希望有更好意见的来提下意见

/**
 * Author 姚智胜
 * Version V1.0版本
 * Description: 显示数据类的父类,必须继承于该类
 * Date: 2017/4/10
 */

public abstract class ImageShowPickerBean {

    public String getImageShowPickerUrl() {
        return setImageShowPickerUrl();
    }

    public int getImageShowPickerDelRes() {
        return setImageShowPickerDelRes();
    }

    /**
     * 为URL赋值,必须重写方法
     *
     * @return
     */
    public abstract String setImageShowPickerUrl();

    /**
     * 为删除label赋值,必须重写方法
     *
     * @return
     */
    public abstract int setImageShowPickerDelRes();


}

4、完善方法

这里要做的其实就是小修小补,比如我们的封装的view ,在最后的时候我又增加了几个自定义属性,让他的可定制性更强,也是在这个步骤里对我们的封装进行最后一次的检查和完善。

以上就是我在写ImageShowPickerView的时候走的几个步骤,会有比我这个更加好的模式,分享出来,就是为了共同进步,和给自己写完一个项目的总结,继续努力,向资深程序员进发!!!