Android自定义View 简单实现多图片选择控件

前言

相信很多朋友在开发中都会遇到图片上传的情况,尤其是多图上传,最

经典的莫过于微信的图片选择了。所有很多情况下会使用到多图选择。

所以就有了这篇文章,今天抽点时间写了个控件。

  • 支持自定义选择图片的样式
  • 支持设置图片选择数量
  • 支持图片预览,删除
  • 支持图片拍照

先来看看效果

实现分析

假如不定义控件,我们要实现这样一个功能,无非是写个GridView在item点击的时候去显示图片进行选择,在返回界面的时候进行GridView的数据刷新。我们把这些逻辑写在我们自定义的GridView中,就成了一个新的控件。

1、GridView的效果展示,逻辑实现。

public class ImagePickerView extends GridView{

    //图片选择数量
    int maxImageSize = 9;

    //添加item布局
    private int noImgResource;

    //列选择数量
    private int columnNumber = 3;

    Activity context;
    ImagesAdapter adapter;

    List<String> imageList;//图片选择list

    private static final int TYPE_SHOW_ADD = 0;
    private static final int TYPE_NO_SHOW_ADD = 1;

    private boolean isShowAdd = true;

    int imageGridSize;

    public void setNoImgResource(int noImgResource) {
        this.noImgResource = noImgResource;
    }

    public void setColumnNumber(int columnNumber) {
        if (columnNumber>5){
            columnNumber = 5;
        }
        this.columnNumber = columnNumber;
        this.setNumColumns(columnNumber);
    }

    public void setShowAdd(boolean showAdd) {
        isShowAdd = showAdd;
    }

    public void setImageList(List<String> imageList) {
        this.imageList = imageList;
        adapter.setImageList(imageList);
    }

    public List<String> getImageList() {
        return imageList;
    }

    public ImagePickerView(Context context) {
        this(context,null);
    }

    public ImagePickerView(Context context, AttributeSet attrs) {
        this(context,attrs,0);
    }

    /**
     * 初始化ImagePickerView的一些信息
     * @param context
     * @param attrs
     * @param defStyle
     */
    public ImagePickerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.context = (Activity) context;
        adapter = new ImagesAdapter();
        this.setAdapter(adapter);
        if (imageList==null){
            imageList = new ArrayList<>();
        }
        this.setNumColumns(columnNumber);
        this.setVerticalSpacing(10);
        this.setHorizontalSpacing(10);
        imageGridSize = (this.context.getWindowManager().getDefaultDisplay().getWidth() - Util.dp2px(context, 2) * 2) / columnNumber;
    }

    /**
     * 提供给外部调用用来再Activity返回时获取图片信息
     * @param requestCode
     * @param resultCode
     * @param data
     */
    public void onActivityResult(int requestCode, int resultCode, Intent data) {

        if (data!=null&& !TextUtils.isEmpty(data.getStringExtra("photoPath"))){//拍照
            imageList.add(data.getStringExtra("photoPath"));
        }else if (data!=null&&data.getSerializableExtra("images")!=null){//图片选择
            imageList = (List<String>) data.getSerializableExtra("images");
        }else{
            List<ImageItem> list = AndroidImagePicker.getInstance().getSelectedImages();
            for (int i=0;i<list.size();i++){
                imageList.add(list.get(i).path);
            }
        }
        AndroidImagePicker.getInstance().setSelectLimit(maxImageSize-imageList.size());
        adapter.setImageList(imageList);
    }

    class ImagesAdapter extends BaseAdapter {

        List<String> imageList;

        public ImagesAdapter() {
            this.imageList = new ArrayList();
        }

        public void setImageList(List<String> imageList) {
            this.imageList = imageList;
            notifyDataSetChanged();
        }

        @Override
        public int getCount() {
            if (isShowAdd){
                if (imageList == null || imageList.isEmpty()) {
                    return 1;
                }
                if (imageList.size() >= maxImageSize) {
                    return maxImageSize;
                }
                return imageList.size() + 1;
            }
            if (imageList.size() >= maxImageSize) {
                return maxImageSize;
            }
            return imageList.size()+1;
        }

        @Override
        public String getItem(int position) {
            if (isShowAdd){
                if (position==imageList.size()){
                    return null;
                }
                return imageList.get(position-1);
            }
            return imageList.get(position);
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public int getViewTypeCount() {
            return 2;
        }

        @Override
        public int getItemViewType(int position) {
            if (isShowAdd){
                return position==imageList.size()?TYPE_SHOW_ADD:TYPE_NO_SHOW_ADD;
            }else{
                return TYPE_NO_SHOW_ADD;
            }
        }

        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {
            int itemViewType = getItemViewType(position);
            if(itemViewType == TYPE_SHOW_ADD){//当前item为添加图片item
                if (noImgResource!=0){//加载用户的添加item布局
                    convertView = LayoutInflater.from(context).inflate(noImgResource, parent, false);
                }else {//默认的添加item布局
                    convertView = LayoutInflater.from(context).inflate(R.layout.grid_item_camera, parent, false);
                }
                convertView.setTag(null);
                convertView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {//点击选择图片
                        Intent intent = new Intent(context, ImagesGridActivity.class);//图片选择
                        Activity activity =  context;
                        activity.startActivityForResult(intent,1001);
                    }
                });
            }else{//普通item,加载图片,并对item设置点击进行预览
                final ViewHolder holder;
                if(convertView == null){
                    convertView = LayoutInflater.from(context).inflate(R.layout.image_grid_item, null);
                    holder = new ViewHolder();
                    holder.ivPic = (SimpleDraweeView)convertView.findViewById(R.id.iv_thumb);
                    holder.cbPanel = convertView.findViewById(R.id.thumb_check_panel);
                    convertView.setTag(holder);
                }else{
                    holder = (ViewHolder) convertView.getTag();
                }
                convertView.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {//将选择的图片与当前postion传过去。
                        Intent intent = new Intent(context, PreviewDelActivity.class);
                        intent.putExtra("images", (Serializable) imageList);
                        intent.putExtra("position",position);
                        context.startActivityForResult(intent,1002);
                    }
                });
                ImageRequestBuilder requestBuilder = ImageRequestBuilder.newBuilderWithSource(
                        Uri.parse(String.format("file://%s", imageList.get(position))))
                        .setResizeOptions(new ResizeOptions(imageGridSize, imageGridSize))
                        .setAutoRotateEnabled(true);
                PipelineDraweeController controller = (PipelineDraweeController) Fresco.newDraweeControllerBuilder()
                        .setOldController(holder.ivPic.getController())
                        .setImageRequest(requestBuilder.build())
                        .build();
                holder.ivPic.setController(controller);
            }
            return convertView;
        }
    }

    class ViewHolder{
        SimpleDraweeView ivPic;
        View cbPanel;
    }
}

代码比较简单,都加了注释。在view的初始化方法中获取一些信息和GridView的展示信息,并且设置适配器关联。

在GridView的 item中设置了点击事件,并且提供给外部用来刷新数据的onActivityResult方法。

大家看上面的代码应该就明白了。

2、图片预览和删除

这一步很简单,获取到传过来的imageList和postion对图片进行展示,在点击删除的时候remove掉imageList对应的数据,并且刷新viewPager

直接上代码

public class PreviewDelActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = ImagePreviewActivity.class.getSimpleName();

    TextView mTitleCount;
    TextView mBtnOk;
    private ImageView backBtn;

    List<String> mImageList;

    int mShowItemPosition = 0;

    ViewPager mViewPager;

    TouchImageAdapter mAdapter ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_preview_del);

        mImageList = (List<String>) getIntent().getSerializableExtra("images");
        mShowItemPosition = getIntent().getIntExtra("position",0);

        mBtnOk = (TextView) findViewById(R.id.btn_del);
        backBtn = (ImageView) findViewById(R.id.btn_backpress);
        mBtnOk.setOnClickListener(this);
        backBtn.setOnClickListener(this);

        mTitleCount = (TextView) findViewById(R.id.tv_title_count);
        mTitleCount.setText(mShowItemPosition+1+"/" + mImageList.size());// 图片数量和当前图片信息展示

        initView();

        AndroidImagePicker.getInstance().clearSelectedImages();
    }

    private void initView() {
        mViewPager = (ViewPager)findViewById(R.id.viewpager);
        mAdapter = new TouchImageAdapter(getSupportFragmentManager());
        mViewPager.setAdapter(mAdapter);
        mViewPager.setCurrentItem(mShowItemPosition, false);//设置显示当前的图片
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                mTitleCount.setText(position+1+"/" + mImageList.size());//滑动viewPager时更新显示信息
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
    }

    @Override
    public void onClick(View v) {
        int i = v.getId();
        if (i == R.id.btn_del) {//删除按钮点击
            mAdapter.remove(mViewPager.getCurrentItem());//
            mTitleCount.setText(mViewPager.getCurrentItem()+1+"/" + mImageList.size());
            if (mImageList.size()==0){
                Intent intent = new Intent();
                intent.putExtra("images", (Serializable) mImageList);
                setResult(RESULT_OK,intent);
                finish();
            }
        }else if (i==R.id.btn_backpress){//返回
            Intent intent = new Intent();
            intent.putExtra("images", (Serializable) mImageList);
            setResult(RESULT_OK,intent);
            finish();
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode==KeyEvent.KEYCODE_BACK){
            Intent intent = new Intent();
            intent.putExtra("images", (Serializable) mImageList);
            setResult(RESULT_OK,intent);
            finish();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    class TouchImageAdapter extends FragmentStatePagerAdapter {
        public TouchImageAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public int getCount() {
            return mImageList.size();
        }

        public void remove(int position){
            mImageList.remove(position);
            notifyDataSetChanged();
        }

        @Override
        public int getItemPosition(Object object) {
            return POSITION_NONE;
        }

        @Override
        public Fragment getItem(int position) {
            SinglePreviewFragment fragment = new SinglePreviewFragment();
            Bundle bundle = new Bundle();
            bundle.putSerializable(SinglePreviewFragment.KEY_URL, mImageList.get(position));
            fragment.setArguments(bundle);
            return fragment;
        }

    }

    @SuppressLint("ValidFragment")
    private class SinglePreviewFragment extends Fragment {
        public static final String KEY_URL = "key_url";
        private PhotoDraweeView photoDraweeView;
        private String url;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Bundle bundle = getArguments();

            url = (String) bundle.getSerializable(KEY_URL);
            Log.i(TAG, "=====current show image path:" + url);

            photoDraweeView = new PhotoDraweeView(getActivity());
            photoDraweeView.setBackgroundColor(0xff000000);
            ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
            photoDraweeView.setLayoutParams(params);

            photoDraweeView.setOnPhotoTapListener(new OnPhotoTapListener() {
                @Override
                public void onPhotoTap(View view, float x, float y) {
                    getActivity().finish();
                }
            });
            if (!url.startsWith("http://") && !url.startsWith("https://")) {
                url = "file://"+url;
            }

            ImageRequestBuilder requestBuilder = ImageRequestBuilder.newBuilderWithSource(
                    Uri.parse(url))
                    .setResizeOptions(new ResizeOptions(768,1280))
                    .setAutoRotateEnabled(true);

            PipelineDraweeControllerBuilder controller = Fresco.newDraweeControllerBuilder();
            controller.setOldController(photoDraweeView.getController());
            controller.setImageRequest(requestBuilder.build());
            controller.setControllerListener(new BaseControllerListener<ImageInfo>() {
                @Override
                public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
                    super.onFinalImageSet(id, imageInfo, animatable);
                    if (imageInfo == null) {
                        return;
                    }
                    photoDraweeView.update(imageInfo.getWidth(), imageInfo.getHeight());
                }
            });
            photoDraweeView.setController(controller.build());
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            return photoDraweeView;
        }

    }

}

相信大家都能看懂上面代码,用ViewPager对图片进行加载,在点击回退和删除完图片的时候把ImageList传回去。

这样在onActivityResult中获取到imageList,刷新adapter,gridview就重新渲染了。

使用

1、布局中引入ImagePickerView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.qiangyu.test.imagepickerviewdemo.MainActivity">
    <com.redare.imagepicker.widget.ImagePickerView
        android:id="@+id/imagePicker"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </com.redare.imagepicker.widget.ImagePickerView>

    <Button
        android:id="@+id/commit_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="提交"/>
</LinearLayout>

2、在Activity中获得ImagePickerView,并且在onActivityResult方法中调用ImagePickerView数据刷新方法

imagePicker.onActivityResult(requestCode,resultCode,data);

3、获取选择图片的路径

调用imagePicker.getImageList()即返回图片选择的List

下面是一段代码示例

public class MainActivity extends AppCompatActivity {

    private ImagePickerView imagePicker;

    private Button commitBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Fresco.initialize(this);

        imagePicker = (ImagePickerView) findViewById(R.id.imagePicker);
        commitBtn = (Button) findViewById(R.id.commit_btn);
        commitBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                for (int i=0;i<imagePicker.getImageList().size();i++){
                    Log.d("yqy",imagePicker.getImageList().get(i));
                }
            }
        });
        imagePicker.setNoImgResource(R.layout.add_img);//自定义imagePicker  add item样式
        imagePicker.setColumnNumber(5);//设置显示5列

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        imagePicker.onActivityResult(requestCode,resultCode,data);
    }
}

结语

看了ImagePickerView的实现,是不是发现一个自定义控件其实也很简单。在我们做自定义控件的时候,其实大部分情况只是在Android系统提供的功能上多加一点我们的需求而已。站在巨人的肩膀上,才可以看的更远嘛。

关于图片加载和图片选择本文没有提及,图片加载我参考了文章,感兴趣的朋友可以去github查看,https://github.com/easonline/AndroidImagePicker。我在自己的Demo中对源码做了修改,并统一使用了Fresco加载图片。

有需要参考源码的同学请参考:源码下载

觉得本文对你有帮助,动动手指关注我、点点赞呗!

时间: 2024-10-11 03:20:58

Android自定义View 简单实现多图片选择控件的相关文章

Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38140505 自从Gallery被谷歌废弃以后,Google推荐使用ViewPager和HorizontalScrollView来实现Gallery的效果.的确HorizontalScrollView可以实现Gallery的效果,但是HorizontalScrollView存在一个很大的问题,如果你仅是用来展示少量的图片,应该是没问题的,但是如果我希望HorizontalScr

Android自定义View(CustomCalendar-定制日历控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeasure 4onDraw 绘制月份 绘制星期 绘制日期及任务 5事件处理 源码下载 ??应项目需求,需要做一个日历控件,效果图如下: ???? ??接到需求后,没有立即查找是否有相关开源日历控件可用.系统日历控件是否能满足 ,第一反应就是这个控件该怎么画?谁叫咱自定义控件技术牛逼呢O(∩_∩)O哈哈~

Android自定义View(三、深入解析控件测量onMeasure)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51490283 本文出自:[openXu的博客] 目录: onMeasure什么时候会被调用 onMeasure方法执行流程 MeasureSpec类 从ViewGroup的onMeasure到View的onMeasure ViewGroup中三个测量子控件的方法 getChildMeasureSpec方法 View的onMeasure setMeasuredDimension ??在上一篇

Android 自定义圆圈进度并显示百分比例控件(纯代码实现)

首先,感谢公司能给我闲暇的时间,来稳固我的技术,让我不断的去探索研究,在此不胜感激. 先不说实现功能,上图看看效果 这个是续上一次水平变色进度条的有一个全新的控件,理论实现原理 1.分析控件:该控件基本上是圆圈内嵌圆圈: 2.进度计算:其实是小学二年级数学题:当前进度/总数=百分比: 3.中间时间:呵呵,纯粹忽悠,不解释(当前时间). 理论总是和实践差距的太远,不扯淡,不吹嘘,贴代码: package com.spring.progressview; import java.text.Simpl

Android开发技巧——定制仿微信图片裁剪控件

拍照--裁剪,或者是选择图片--裁剪,是我们设置头像或上传图片时经常需要的一组操作.上篇讲了Camera的使用,这篇讲一下我对图片裁剪的实现. 背景 下面的需求都来自产品. 裁剪图片要像微信那样,拖动和放大的是图片,裁剪框不动. 裁剪框外的内容要有半透明黑色遮罩. 裁剪框下面要显示一行提示文字(这点我至今还是持保留意见的). 在Android中,裁剪图片的控件库还是挺多的,特别是github上比较流行的几个,都已经进化到比较稳定的阶段,但比较遗憾的是它们的裁剪过程是拖动或缩放裁剪框,于是只好自己

android自定义View&amp;&amp;简单布局&amp;&amp;回调方法

一.内容描述 根据“慕课网”上的教程,实现一个自定义的View,且该View中使用自定义的属性,同时为该自定义的View定义点击事件的回调方法. 二.定义自定义的属性 在res/valus/ 文件夹下创建一个 attrs.xml 的属性定义文件,内容如下: <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="HeadBar&qu

Android自定义View(RollWeekView-炫酷的星期日期选择控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/53420889 本文出自:[openXu的博客] 目录: 1分析 2定义控件布局 3定义CustomWeekView 4重写onMeasure 5点击后执行动画 7重置预备控件 源码下载 ??最近收到一个自定义控件的需求,需要做一个日期选择控件,实现图如下: ???? ??一次展示一个星期的5天,中间放大的为当前选中的:如果点击了其中一个日期,比如星期五,那么整体向左滑动,并将星期五慢慢放大

Android自定义View(LineBreakLayout-自动换行的标签容器)

??最近一段时间比较忙,都没有时间更新博客,今天公司的事情忙完得空,继续为我的自定义控件系列博客添砖加瓦.本篇博客讲解的是标签自动换行的布局容器,正好前一阵子有个项目中需要,想了想没什么难度就自己弄了.而自定义控件系列文章中对于自定义ViewGroup上次只是讲解了一些基础和步骤 Android自定义ViewGroup(四.打造自己的布局容器),这次就着这个例子我们来完成一个能在项目中使用的自定义布局容器. 1. 初步分析 ??首先我们看一看要完成的效果图: ?????? ??上面红色标示出的就

【Android自定义View实战】之超简单SearchView

[Android自定义View实战]之超简单SearchView 在Android开发中我们经常会用到搜索框,而系统提供的又不尽完美.所以自定义一个比较简单的SearchView. 效果图 实现代码 package cn.bluemobi.dylan.searchview; import android.content.Context; import android.text.Editable; import android.text.TextWatcher; import android.ut