Android 项目:画图白板APP开发(六)——分页展示
本篇将介绍如何为我们的画板应用添加分页展示功能,让用户可以创建多个画布并在它们之间轻松切换。这章没有啥知识点的讲解,主要介绍一下每页保存的数据结构是什么样的。
一、ListView
多页数据的管理我们使用ListView。之前有文章讲过ListView这里就不多赘述了,感兴趣的读者可以看看。Android最常用的控件ListView(详解) 。
直接上图例和代码:
//绑定适配器(传入handler)
adapter = new PictureAdapter(mContext,R.layout.list_item,listDate,handler);
viewMember.lv_tables.setAdapter(adapter);
(1)PictureView.java
//保存某一页的视图信息
public class PictureView {//保存比例信息Matrix matrixMain = new Matrix();//保存撤销和恢复的信息private ArrayList<PaintDates> paintedList = new ArrayList<>();public ArrayList<MessageStrokes> getCancelList() {return cancelList;}public void setCancelList(ArrayList<MessageStrokes> cancelList) {this.cancelList = cancelList;}public ArrayList<MessageStrokes> getRecoverList() {return recoverList;}public void setRecoverList(ArrayList<MessageStrokes> recoverList) {this.recoverList = recoverList;}private ArrayList<MessageStrokes> cancelList = new ArrayList<>();private ArrayList<MessageStrokes> recoverList = new ArrayList<>();//设置一个专门为撤销,回退服务的list//用来保存每一个操作的意义(可能是单笔的,可能是多笔)//view上private Bitmap cacheBitmap;private Canvas cacheCanvas ;public PictureView(int width, int height) {cacheBitmap = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_4444);cacheCanvas = new Canvas(cacheBitmap);}public ArrayList<PaintDates> getPaintedList() {return paintedList;}public void setPaintedList(ArrayList<PaintDates> paintedList) {this.paintedList = paintedList;}public Bitmap getCacheBitmap() {return cacheBitmap;}public void setCacheBitmap(Bitmap cacheBitmap) {this.cacheBitmap = cacheBitmap;}public Canvas getCacheCanvas() {return cacheCanvas;}public void setCacheCanvas(Canvas cacheCanvas) {this.cacheCanvas = cacheCanvas;}}
PictureView
是一个数据模型类,用于保存画板中某一页的完整状态信息。
- (
cacheBitmap
和cacheCanvas
):保存当前页面的最终渲染结果
paintedList:
存储所有的笔画数据
cancelList
:存储已执行但可撤销的操作
recoverList
:存储已撤销但可恢复的操作
matrixMain
:保存缩放、平移、旋转等变换信息
(2)PictureAdapter.java
//适配器
public class PictureAdapter extends ArrayAdapter<PictureView> {//用来判断当前View上显示的时哪个(默认为第一个)public int localNum = 1;private Handler handler;public PictureAdapter(@NonNull Context context, int resource, @NonNull List<PictureView> objects, Handler handler) {super(context, resource, objects);this.handler = handler;}@SuppressLint("SetTextI18n")@NonNull@Overridepublic View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {PictureView p = getItem(position);View view;//新增一个内部类 ViewHolder,用于对控件的实例进行缓存ViewHolder viewHolder;if (convertView==null){//为每一个子项加载设定的布局view= LayoutInflater.from(getContext()).inflate(R.layout.list_item,parent,false);viewHolder= new ViewHolder();//分别获取 imageview 和 textview 的实例viewHolder.image =view.findViewById(R.id.iv_image);viewHolder.imageNum =view.findViewById(R.id.tv_num);viewHolder.imageDelete=view.findViewById(R.id.bt_delete_item);viewHolder.layout = view.findViewById(R.id.fl_item);view.setTag(viewHolder);//将 viewHolder 存储在 view 中}else {view=convertView;viewHolder= (ViewHolder) view.getTag();//重新获取 viewHolder}// 设置要显示的内容viewHolder.image.setImageBitmap(p.getCacheBitmap());viewHolder.imageNum.setText((position+1)+"");if((position+1)==localNum){//008FFBviewHolder.imageNum.setTextColor(Color.parseColor("#008FFB"));}else {viewHolder.imageNum.setTextColor(Color.WHITE);}//按钮点击事件(使用handler)viewHolder.imageDelete.setOnClickListener(v->{//创建一个线程Thread t = new Thread(() -> {Message m = handler.obtainMessage();m.what = 0x101;m.arg1 = position;handler.sendMessage(m);});t.start();});viewHolder.layout.setOnClickListener(v->{Thread t =new Thread(() -> {Message m = handler.obtainMessage();m.what = 0x102;m.arg1 = position;handler.sendMessage(m);});t.start();});return view;}private static class ViewHolder {TextView imageNum;ImageView image;ImageButton imageDelete;FrameLayout layout;}}
PictureAdapter中的点击事件,通过Handler传递。
(3)Handler
@SuppressLint("HandlerLeak")
private void initHandler() {handler = new Handler(Looper.getMainLooper()){@SuppressLint("SetTextI18n")@Overridepublic void handleMessage(@NonNull Message msg) {switch (msg.what){case 0x101://删除//弹出提升框now = msg.arg1;new AlertDialog.Builder(mContext).setTitle("提示").setMessage("确定要删除 "+ (now+1) +"号视图吗?").setPositiveButton("确定", (dialogInterface, i) -> {//是否为删除的为当前显示的视图System.out.println("AAAAAAAAAA: "+ (now+1) +" "+NowNum);if((now+1)==NowNum&&now==0){if(total == 1){Toast.makeText(mContext, "您无法删除最后一个视图", Toast.LENGTH_SHORT).show();}else {total--;//清空内存clearBitmap(listDate.get(now));listDate.remove(now);adapter.localNum = NowNum;blackboardView1.updateView(NowNum-1);}}else if((now+1)==NowNum&&now!=0){//向上移动total--;NowNum--;clearBitmap(listDate.get(now));listDate.remove(now);adapter.localNum = NowNum;blackboardView1.updateView(NowNum-1);}else if((now+1)!=NowNum&&(now+1)>NowNum){//不动total--;clearBitmap(listDate.get(now));listDate.remove(now);}else {//整体上移total--;NowNum--;clearBitmap(listDate.get(now));listDate.remove(now);adapter.localNum = NowNum;blackboardView1.updateView(NowNum-1);}viewMember.tv_whereForNum.setText(numToString(NowNum)+"/"+numToString(total));adapter.notifyDataSetChanged();}).setNegativeButton("取消",null).show();break;case 0x102://点击试图切换now = msg.arg1;NowNum = now+1;adapter.localNum = NowNum;//发送消息进行上传(目前感觉没必要上传)
// Message message = new Message();
// message.what = 16;
// message.arg1 = NowNum;
// System.out.println("popopopo nowNum:"+NowNum);
// //operateHandler//同时更新的还有底部的数字viewMember.tv_whereForNum.setText(numToString(NowNum)+"/"+numToString(total));
// oldBitmap = blackboardView1.cacheBitmap;
// operateHandler.sendEmptyMessage(100);//通知截屏上传(在没更新之前)blackboardView1.updateView(NowNum-1);adapter.notifyDataSetChanged();break;case 0x103://漫游:显示比例数值viewMember.tv_zoomNum.setText(FTOString((Float) msg.obj));break;case 0x104://down的是时候不让获取获取焦点viewMember.bt_tables.setEnabled(false);viewMember.bt_last.setEnabled(false);viewMember.bt_next.setEnabled(false);viewMember.bt_add.setEnabled(false);viewMember.tv_whereForNum.setEnabled(false);Resources resources_table = mContext.getResources();if (viewMember.lv_tables.getVisibility() == View.VISIBLE) {viewMember.lv_tables.setVisibility(View.GONE);viewMember.tv_whereForNum.setTextColor(Color.WHITE);}if(viewMember.ll_more.getVisibility()==View.VISIBLE){viewMember.ll_more.setVisibility(View.GONE);Drawable imageDrawable = resources_table.getDrawable(R.drawable.tables_uncheck);viewMember.bt_tables.setBackground(imageDrawable);}//开始工具类的按钮//1.首先要让下面一排子的东西点不了viewMember.bt_pen.setEnabled(false);viewMember.bt_eraser.setEnabled(false);viewMember.bt_revoke.setEnabled(false);viewMember.bt_recover.setEnabled(false);viewMember.bt_zoom.setEnabled(false);//2.布局恢复if(viewMember.ll_penWidth.getVisibility() == View.VISIBLE){//这个就证明在画笔的行列viewMember.bt_width_1.setEnabled(false);viewMember.bt_width_2.setEnabled(false);viewMember.bt_width_3.setEnabled(false);viewMember.bt_width_4.setEnabled(false);viewMember.bt_width_5.setEnabled(false);viewMember.bt_penColor.setEnabled(false);if(viewMember.ll_colorAndAlpha.getVisibility() == View.VISIBLE){viewMember.ll_colorAndAlpha.setVisibility(View.GONE);}}if(viewMember.ll_eraser.getVisibility() == View.VISIBLE){viewMember.bt_son_eraser.setEnabled(false);viewMember.bt_handwriting_eraser.setEnabled(false);viewMember.sb_clear_sliding.setEnabled(false);}break;case 0x105://up的时候解封viewMember.bt_tables.setEnabled(true);viewMember.bt_last.setEnabled(true);viewMember.bt_next.setEnabled(true);viewMember.bt_add.setEnabled(true);viewMember.tv_whereForNum.setEnabled(true);viewMember.bt_pen.setEnabled(true);viewMember.bt_eraser.setEnabled(true);viewMember.bt_revoke.setEnabled(true);viewMember.bt_recover.setEnabled(true);viewMember.bt_zoom.setEnabled(true);if(viewMember.ll_penWidth.getVisibility() == View.VISIBLE){//这个就证明在画笔的行列viewMember.bt_width_1.setEnabled(true);viewMember.bt_width_2.setEnabled(true);viewMember.bt_width_3.setEnabled(true);viewMember.bt_width_4.setEnabled(true);viewMember.bt_width_5.setEnabled(true);viewMember.bt_penColor.setEnabled(true);}if(viewMember.ll_eraser.getVisibility() == View.VISIBLE){viewMember.bt_son_eraser.setEnabled(true);viewMember.bt_handwriting_eraser.setEnabled(true);viewMember.sb_clear_sliding.setEnabled(true);}break;case 0x106: //电子笔清除屏幕new AlertDialog.Builder(mContext).setTitle("提示").setMessage("确定要清屏吗?").setPositiveButton("确定", (dialogInterface, i) -> {blackboardView1.clear();blackboardView1.isDialog = false;}).setNegativeButton("取消",(dialogInterface, i) -> {blackboardView1.isDialog = false;}).show().setCanceledOnTouchOutside (false);blackboardView1.clear_hardware();//清除其他笔画}super.handleMessage(msg);}};
}
Handler 涉及到了功能:这里涉及到后面要讲的功能,这里简单说下
0x101 :当ListView点击删除时调用,弹出 AlertDialog 要求用户确定操作。
0x102 :点击切换视图,主界面显示对应页的画布。
0x103 :放大缩小时,显示比例数值。比如50% , 300%。
0x104 :用户画线的时候,不允许操作ListView。
0x105 :没有写画时允许操作。
0x106:电子笔点击按钮后,调用清屏功能。
(4)更新画布 updateView
//切换视图,刷新
public void updateView(int whereView){//对所有数据进行更新ViewNum = whereView;mPaintedList = mListDate.get(ViewNum).getPaintedList();mCancelList = mListDate.get(ViewNum).getCancelList();mRecoverList = mListDate.get(ViewNum).getRecoverList();cacheBitmap = mListDate.get(ViewNum).getCacheBitmap();cacheCanvas = mListDate.get(ViewNum).getCacheCanvas();mMatrixMain = mListDate.get(ViewNum).matrixMain;//传一下handlermMatrixMain.getValues(mainDate);Message m = this.handler.obtainMessage();m.what = 0x103;m.obj = mainDate[0];this.handler.sendMessage(m);cacheCanvas.drawColor(0,PorterDuff.Mode.CLEAR);bottomCanvas.drawColor(0,PorterDuff.Mode.CLEAR);invalidateReason = REASON_RE;invalidate();
}
将 PictureView 中的对象赋值给当前视图即可。本章节篇幅较少,主要是介绍多画布的框架,为后面的章节打好基础。