人生的磨难是很多的,所以我们不可对于每一件轻微的伤害都过于敏感。在生活磨难面前,精神上的坚强和无动于衷是我们抵抗罪恶和人生意外的最好武器。 —— 洛克
UI界面片段效果图:
一、构思:
(1)没有需求文档说明:首先我们会根据平常用常用的App来进行构思,例如:微信、QQ、其他社交软件、其他贷款App等。
红色箭头1表示对要上传的图片做本地和网络的删除。
红色箭头2表示没有添加任何图片情况下默认图标的背景图(中间为白色,边框为灰色的背景图)。
红色箭头3表示成功上传图片后的图片并显示成圆角。
红色箭头4表示没有添加任何图片的情况下默认前景图片。
(2)有需求文档说明:作为开发者不一定是根据需求文档照抄照搬,需求文档拟定的那个人或者产品经理不一定很完善,需要你编码者多与之沟通,同时沟通让UI页面变活,同时也逐渐完善了需求文档。
二、准备开发材料
开发材料包括:UI图标、开源库。
UI设计把切好的图上传到蓝湖,方便下载Android 或者IOS多套图。在与UI沟通的过程中,我们需要考虑我们需要哪些图标,例如:红色箭头1表示删除的图标、红色箭头2表示边框的图标、红色箭头4表示相机的图标。至于红色箭头2边框的图标也可以我们自定义xml来进行实现,实现工具http://shapes.softartstudio.com/
自定义边框背景:take_local_pic_border.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners
android:topLeftRadius="6dp"
android:topRightRadius="6dp"
android:bottomRightRadius="6dp"
android:bottomLeftRadius="6dp">
</corners>
<stroke
android:width="2dp"
android:color="#999999">
</stroke>
<solid
android:color="#fefefe">
</solid>
</shape>
开源库:图片加载开源库(glide、Picasso、Image-Loader),可选择其中之一进行开发。
由于开发习惯的不同,我首选glide进行加载网络图片
列表控件:GridViewGridView官方说明文档
列表适配器:BaseAdapter 适配器官方说明文档
展示图片控件:ImageView 说明文档
三、开始编码
- 1、在gradle配置glide依赖:
implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
Application模块中创建 @GlideModule
注解,继承自 AppGlideModule
的类。
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
@GlideModule
public final class MyAppGlideModule extends AppGlideModule {}
执行 Make Module 'app' ,工具栏貌似锤子的图标,很遗憾报错了,按照官方文档进行操作还有错吗???
不要慌,仔细查看控制台的错误信息,编译失败。gradle配置targetSdkVersion 29 对应的是google的AndroidX,那么可能是这个原因,无法加载android.support.annotation包下api,那么谷歌官方肯定针对这个问题提供了相关的依赖包
androidx.annotation:annotation:1.0.0
最后添加下面的依赖到gradle并执行Make Module 'app',很高兴编译成功了,折腾了一小会总算可以继续往下走了,多大的事貌似就被针扎了一下,接下来调用GlieApp中静态放心加载图片:
annotationProcessor 'androidx.annotation:annotation:1.0.0'
加载图片工具类:
public static void loadImagePic(Context context, String url, ImageView imageView) {
GlideApp.with(context)
.load(url)
.error(R.drawable.ic_launcher_background)
.placeholder(R.drawable.ic_launcher_background)
.into(imageView);
}
- 2、创建列表布局文件 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<GridView
android:id="@+id/gvPic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:horizontalSpacing="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="80dp"
android:numColumns="4"
tools:ignore="MissingPrefix" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
- 3、创建列表每一项布局文件gv_camera_pic_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/ivCameraPicDefault"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/iv_camera_round_bg"
android:scaleType="center"
android:src="@drawable/ic_camera" />
<ImageView
android:id="@+id/ivCameraPic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />
<ImageView
android:id="@+id/ivDelete"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:background="@drawable/ic_delete"
android:visibility="gone" />
</RelativeLayout>
- 4、创建列表适配器:
适配器配器构造函数:启动Activity完成初始化就将picDefaultId相机图片添加到数组队列中,添加点击事件是方便点击相机图标进行选择手相册图片或拍照:
ImageAdapter(Context c, int picDefaultId, GvItemClick gvItemClick),
点击默认相机图标就会在图片数组中添加成功选择或者拍照的图片; 点击新增图片右上角的删除图标,对当前在该数组存在的图片进行逐个删除. changeAdapterAddData(Integer picId)和changeAdapteRemoverData(int i)
计算每张图片需要的宽与高,运用这个算法获取的宽度,然后动态为ImageView添加布局参数。 public int getGvItemWidth(Context context) { Display display = ((Activity) context).getWindowManager().getDefaultDisplay(); int width = display.getWidth(); //int height = display.getHeight(); return (width - dip2px(context, 50)) / 4; }
public class ImageAdapter extends BaseAdapter {
private Context mContext;
private List<Integer> mPics = new ArrayList<>();
private GvItemClick mGvItemClick;
public ImageAdapter(Context c, int picDefaultId, GvItemClick gvItemClick) {
mContext = c;
mPics.add(picDefaultId);
this.mGvItemClick = gvItemClick;
}
/**
* 刷新列表数据
*
* @param picId
*/
public void changeAdapterAddData(Integer picId) {
mPics.add(0, picId);
//mPics.addAll(0, picIds);
notifyDataSetChanged();
}
public void changeAdapteRemoverData(int i) {
mPics.remove(i);
//mPics.addAll(0, picIds);
notifyDataSetChanged();
}
@Override
public int getCount() {
return mPics.size();
}
@Override
public Object getItem(int position) {
return mPics.size() > 0 ? mPics.get(position) : null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if (convertView == null) {
convertView = View.inflate(mContext, R.layout.gv_camera_pic_item, null);
viewHolder = new ViewHolder();
viewHolder.ivCameraPic = convertView.findViewById(R.id.ivCameraPic);
viewHolder.ivCameraPicDefault = convertView.findViewById(R.id.ivCameraPicDefault);
viewHolder.ivDelete = convertView.findViewById(R.id.ivDelete);
//设置每一项宽和高
int width = getGvItemWidth(mContext);
viewHolder.ivCameraPic.setLayoutParams(new RelativeLayout.LayoutParams(width, width));
viewHolder.ivCameraPicDefault.setLayoutParams(new RelativeLayout.LayoutParams(width, width));
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
//End item
if (position == getCount() - 1) {
viewHolder.ivDelete.setVisibility(View.GONE);
viewHolder.ivCameraPicDefault.setVisibility(View.VISIBLE);
viewHolder.ivCameraPic.setVisibility(View.GONE);
viewHolder.ivCameraPicDefault.setImageResource(mPics.get(position));
} else {
viewHolder.ivDelete.setVisibility(View.VISIBLE);
viewHolder.ivCameraPicDefault.setVisibility(View.GONE);
viewHolder.ivCameraPic.setVisibility(View.VISIBLE);
viewHolder.ivCameraPic.setImageResource(mPics.get(position));
}
viewHolder.ivDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mGvItemClick != null)
mGvItemClick.gvItemClick(R.id.ivDelete, position);
}
});
viewHolder.ivCameraPicDefault.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mGvItemClick != null)
mGvItemClick.gvItemClick(R.id.ivCameraPicDefault, position);
}
});
return convertView;
}
public static interface GvItemClick {
void gvItemClick(int viewId, int postion);
}
/**
* 获取gv每一项宽度
*
* @param context
* @return
*/
public int getGvItemWidth(Context context) {
Display display = ((Activity) context).getWindowManager().getDefaultDisplay();
int width = display.getWidth();
//int height = display.getHeight();
return (width - dip2px(context, 50)) / 4;
}
public class ViewHolder {
ImageView ivDelete;
ImageView ivCameraPic;
ImageView ivCameraPicDefault;
}
/**
* dp转px
*
* @param context
* @param dpValue
* @return
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* px转dp
*
* @param context
* @param pxValue
* @return
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
}
- 5、为GridView列表添加适配器:
private GridView mGvPic;
private ImageAdapter mImageAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageAdapter = new ImageAdapter(this, R.drawable.ic_camera, this);
mGvPic = findViewById(R.id.gvPic);
mGvPic.setAdapter(mImageAdapter);
}
- 6、为相机图标(即列表最后一项)添加点击事件:
currentNum的定义是为了控制从相册中添加图标或者拍照上传成功后图片的数量,业务需求只允许上传N张图片,那么这个N张就是你想要上传的数量了.
private int currentNum = 0;
private void addPicData() {
if (currentNum < 4) {
switch (currentNum) {
case 0:
mImageAdapter.changeAdapterAddData(R.drawable.image_1);
break;
case 1:
mImageAdapter.changeAdapterAddData(R.drawable.image_2);
break;
case 2:
mImageAdapter.changeAdapterAddData(R.drawable.image_3);
break;
case 3:
mImageAdapter.changeAdapterAddData(R.drawable.image_4);
break;
}
currentNum++;
System.out.println(">>>>>>>>>>>>>>>" + currentNum);
}
}
- 7、为每一项成功添加并上传到服务端的图片右上角的删除图标添加点击事件;
private void removePicData(int position) {
if (currentNum > 0) {
mImageAdapter.changeAdapteRemoverData(position);
currentNum--;
System.out.println(">>>>>>>>>>>>>>>" + currentNum);
}
}
总结
在列表中进行添加或者删除图标,我们需要一步一步来实现功能,细小的功能才能逐渐完善才能带给用户更好的体验,后续将持续讲解手机相册图片的选择和拍照并实现上传到服务端。