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加载图片。
有需要参考源码的同学请参考:源码下载
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。