热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

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

这篇文章主要为大家详细介绍了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 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 imageList) {
 this.imageList = imageList;
 adapter.setImageList(imageList);
 }

 public List 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) data.getSerializableExtra("images");
 }else{
  List list = AndroidImagePicker.getInstance().getSelectedImages();
  for (int i=0;i imageList;

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

 public void setImageList(List 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()&#63;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 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) 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() {
  @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

<&#63;xml version="1.0" encoding="utf-8"&#63;>

 
 

 

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

结语

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

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • 提升Android开发效率:Clean Code的最佳实践与应用
    在Android开发中,提高代码质量和开发效率是至关重要的。本文介绍了如何通过Clean Code的最佳实践来优化Android应用的开发流程。以SQLite数据库操作为例,详细探讨了如何编写高效、可维护的SQL查询语句,并将其结果封装为Java对象。通过遵循这些最佳实践,开发者可以显著提升代码的可读性和可维护性,从而加快开发速度并减少错误。 ... [详细]
  • 本文深入探讨了Java多线程环境下的同步机制及其应用,重点介绍了`synchronized`关键字的使用方法和原理。`synchronized`关键字主要用于确保多个线程在访问共享资源时的互斥性和原子性。通过具体示例,如在一个类中使用`synchronized`修饰方法,展示了如何实现线程安全的代码块。此外,文章还讨论了`ReentrantLock`等其他同步工具的优缺点,并提供了实际应用场景中的最佳实践。 ... [详细]
  • SSL 错误:目标主机名与备用证书主题名称不匹配
    在使用 `git clone` 命令时,常见的 SSL 错误表现为:无法访问指定的 HTTPS 地址(如 `https://ip_or_domain/xxxx.git`),原因是目标主机名与备用证书主题名称不匹配。这通常是因为服务器的 SSL 证书配置不正确或客户端的证书验证设置有问题。建议检查服务器的 SSL 证书配置,确保其包含正确的主机名,并确认客户端的证书信任库已更新。此外,可以通过临时禁用 SSL 验证来排查问题,但请注意这会降低安全性。 ... [详细]
  • 在开发过程中,我最初也依赖于功能全面但操作繁琐的集成开发环境(IDE),如Borland Delphi 和 Microsoft Visual Studio。然而,随着对高效开发的追求,我逐渐转向了更加轻量级和灵活的工具组合。通过 CLIfe,我构建了一个高度定制化的开发环境,不仅提高了代码编写效率,还简化了项目管理流程。这一配置结合了多种强大的命令行工具和插件,使我在日常开发中能够更加得心应手。 ... [详细]
  • 提升 Kubernetes 集群管理效率的七大专业工具
    Kubernetes 在云原生环境中的应用日益广泛,然而集群管理的复杂性也随之增加。为了提高管理效率,本文推荐了七款专业工具,这些工具不仅能够简化日常操作,还能提升系统的稳定性和安全性。从自动化部署到监控和故障排查,这些工具覆盖了集群管理的各个方面,帮助管理员更好地应对挑战。 ... [详细]
  • ButterKnife 是一款用于 Android 开发的注解库,主要用于简化视图和事件绑定。本文详细介绍了 ButterKnife 的基础用法,包括如何通过注解实现字段和方法的绑定,以及在实际项目中的应用示例。此外,文章还提到了截至 2016 年 4 月 29 日,ButterKnife 的最新版本为 8.0.1,为开发者提供了最新的功能和性能优化。 ... [详细]
  • REST与RPC:选择哪种API架构风格?
    在探讨REST与RPC这两种API架构风格的选择时,本文首先介绍了RPC(远程过程调用)的概念。RPC允许客户端通过网络调用远程服务器上的函数或方法,从而实现分布式系统的功能调用。相比之下,REST(Representational State Transfer)则基于资源的交互模型,通过HTTP协议进行数据传输和操作。本文将详细分析两种架构风格的特点、适用场景及其优缺点,帮助开发者根据具体需求做出合适的选择。 ... [详细]
  • 本文介绍了如何在 Windows 系统上利用 Docker 构建一个包含 NGINX、PHP、MySQL、Redis 和 Elasticsearch 的集成开发环境。通过详细的步骤说明,帮助开发者快速搭建和配置这一复杂的技术栈,提升开发效率和环境一致性。 ... [详细]
  • 本文探讨了如何有效地构建和优化微信公众平台账号,涵盖了用户信息管理、内容创作与发布、互动策略及数据分析等方面。通过合理设置用户信息字段,如用户名、昵称、密码、真实姓名和性别等,确保账号的安全性和用户体验。同时,文章还介绍了如何利用微信公众平台的各项功能,提升用户参与度和品牌影响力。 ... [详细]
  • 本文探讨了资源访问的学习路径与方法,旨在帮助学习者更高效地获取和利用各类资源。通过分析不同资源的特点和应用场景,提出了多种实用的学习策略和技术手段,为学习者提供了系统的指导和建议。 ... [详细]
  • 掌握这些技巧,轻松获取超过90%的资源信息
    在数字时代,高效获取所需资源是每个人必备的技能。本文将分享一系列实用技巧,帮助读者轻松获取超过90%的网络资源信息,无论是学术资料、技术文档还是最新资讯,都能迅速找到。通过优化搜索引擎使用、利用专业数据库和社群资源等方法,读者将能够在信息海洋中游刃有余。 ... [详细]
  • 如何高效利用Hackbar插件提升网页调试效率
    通过合理利用Hackbar插件,可以显著提升网页调试的效率。本文介绍了如何获取并使用未包含收费功能的2.1.3版本,以确保在不升级到最新2.2.2版本的情况下,依然能够高效进行网页调试。此外,文章还提供了详细的使用技巧和常见问题解决方案,帮助开发者更好地掌握这一工具。 ... [详细]
  • 微信平台通过盛派SDK(sdk.weixin.senparc.com)允许服务号和订阅号使用appId和token读取关注用户的个人信息。然而,这一过程需严格遵守隐私保护和数据安全的相关规定,确保用户数据的安全性和隐私性。 ... [详细]
  • 开源实习机会 | Compiler SIG 正式发布实习任务,诚邀您加入申请!
    对编译技术充满兴趣却苦于无从入手?当前疫情形势下,外出实习变得困难重重?现在,Compiler SIG 正式发布了一系列实习任务,为有志之士提供了宝贵的机会。无论你是初学者还是有一定基础的学生,都能在这里找到适合自己的实践项目。我们诚挚邀请您的加入,共同探索编译技术的无限可能! ... [详细]
  • HTML 页面中调用 JavaScript 函数生成随机数值并自动展示
    在HTML页面中,通过调用JavaScript函数生成随机数值,并将其自动展示在页面上。具体实现包括构建HTML页面结构,定义JavaScript函数以生成随机数,以及在页面加载时自动调用该函数并将结果呈现给用户。 ... [详细]
author-avatar
中科蓝天李跃华
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有