Fork me on GitHub

手把手教你如何搭建一个自己的安卓快速开发框架之带你做自己的APP(四)

点击查看上一篇文章:手把手教你如何搭建一个自己的安卓快速开发框架之带你做自己的APP(三)

继上一篇我们的开发,包含

  • BaseFragment
  • 精美的仿微信底部菜单栏
  • 网络请求失败时如何显示空View

那么,这一篇,我准备完成:

  • 精美的订单追踪页面
  • fragment+viewpage实现完整订单查看模块
  • baseAdapter+下拉刷新
  • litepal数据库操作

其实到上一篇为止,我们的快速开发框架已经基本完成了。所以呢,接下来我准备在此基础上开发我们的一款APP—–玩快递

上面所说的准备完成的4部分其实我已经做完了,代码在git上,所以这儿我只是带大家看一下,就不细说。

1、开门见山,看下什么是精美的订单追踪页面?

这里写图片描述

如上:我们使用 recycleview 来实现。

首先是一个自定义View,这个自定义 View 包含三部分,

  • beginline
  • mark
  • endline

这里写图片描述

代码在这里:
https://github.com/qht1003077897/AppFrame/blob/master/app/src/main/java/com/qht/blog2/View/TimeLineMarkerView.java

使用了自定义属性,以便在 adapter 中设置这三个位置的 drawable,比如图中的红点

  • 如果已经签收,setbeginline(null); setmark(color.red);
  • 如果在途中,则不用设置,使用 xml 中定义的即可。
  • 如果是第一条信息,即为开始派件,则 setendline(null);

然后 View 的左边是订单时间,右边是具体信息,下面加了一条 height=1px的 view 作为分割线,并且设置 recycleview 的分割线为 null。

接下来看下我们 recycleview 所使用的 Adapter,考虑到 recycleview 的通用性,以及我们的订单页面使用的也是 recycleview,所以我们需要一个 BaseAdapter。

在 github上找了一个,算是比较强大的,并且作者一直在维护:RecycleViewBaseAdapter

看下我们是怎么用的,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* Created by QHT on 2017-04-12.
* 订单详情页面
*/
public class OrderDetail_RV_Adapter extends BaseQuickAdapter<OrderInfoDetailBean,BaseViewHolder> {
private Context context;
private TimeLineMarkerView timelineView;
private String state;
public OrderDetail_RV_Adapter(List<OrderInfoDetailBean> list,String state,Context context) {
super(R.layout.activity_orderdetail_rv_item, list);
this.context=context;
this.state=state;
}
@Override
protected void convert(BaseViewHolder helper, OrderInfoDetailBean item) {
if(timelineView==null ) {
timelineView = (TimeLineMarkerView) (helper.getView(R.id.tv_rv_activity_orderdetail_timelineview));
}
if(helper.getItemViewType()==OrderSite.BEGIN){
timelineView.setBeginLine(null);
helper.setTextColor(R.id.tv_rv_activity_orderdetail_time,context.getResources().getColor(R.color.red));
helper.setTextColor(R.id.tv_rv_activity_orderdetail_content,context.getResources().getColor(R.color.red));
timelineView.setMarkerDrawable(context.getResources().getDrawable(R.drawable.timeline_bg_red));
} else if(helper.getItemViewType()==OrderSite.END){
timelineView.setEndLine(null);
}
helper.setText(R.id.tv_rv_activity_orderdetail_content, item.getContext());
//分两行显示
helper.setText(R.id.tv_rv_activity_orderdetail_time,
item.getTime().toString().substring(0,10)+"\n"+
item.getTime().toString().substring(10));
}
@Override
public int getItemViewType(int position) {
if(position==0 && state.equals("3")){
return OrderSite.BEGIN;
}else if(position==getItemCount()-1){
return OrderSite.END;
}
return super.getItemViewType(position);
}
}

我们在 getItemViewType 类里面判断 item 的类型,返回不同的 int 值:

  • OrderSite.BEGIN 代表订单已经签收。
  • OrderSite.END 代表订单开始派发。

然后在convert 方法中对 viewtype 进行判断,若为签收状态,则不显示 beginline,且 mark 为红色标示;若为派单状态,则 endline 不显示。


2、接下来我们需要花很多时间看这个地方–订单列表 ↓

这里写图片描述

看图,在这个页面中,包含了以下几个要点:

  • recycleview 的下拉刷新
  • recycleview 的右移动画
  • Activity 和 Fragment 积极通信
  • 单选、多选、全选删除动作
  • litepal 增删改查

你有没有注意到,在上个页面,我们的右下角有个浮动按钮,点击是这样的效果:

这里写图片描述

噢,是将这个订单标记为已签收或者未签收状态,所以,我们还用到了数据库。

这里写图片描述

头大,但是也得看下去!

首先,我们有这样一个model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class OrderInfoLitePal extends DataSupport {
int id;
String nu;
String com;
String state;
String time;
boolean isselect;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
............................

isselect 字段的目的是为了解决 checkbox 状态错乱的问题。

extends DataSupport 是 litepal 所需,id 是每条数据的 Id,后面会方便删除。

1. 先来看下拉刷新:

下拉刷新我们用到了这个库 pullrefreshlayout

用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
swipeRefreshLayoutOrderall.setOnRefreshListener(new PullRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
swipeRefreshLayoutOrderall.post(new Runnable() {
@Override
public void run() {
QueryData();
notifydata();
swipeRefreshLayoutOrderall.setRefreshing(false);
}
});
}
});

2. 衔接上,下面是数据库查询操作 :

使用 findAll 方法查一遍数据库,更新 list,再刷新视图即可。

1
2
3
4
5
6
7
8
9
/**
* 避免调用notify失败
*/
public void QueryData() {
list.clear();
List<OrderInfoLitePal> lists=new ArrayList<OrderInfoLitePal>();
lists= DataSupport.findAll(OrderInfoLitePal.class);
list.addAll(lists);
}

为了避免调用 notifyDataSetChanged 失败,采用上面的办法。

1
lists= DataSupport.findAll(OrderInfoLitePal.class);

这句代码即为查询数据库中 表名为 OrderInfoLitePal 的所有数据。

因为是全部订单,所以使用 findAll 。
如果是已签收订单(state为3代表已签收,其余状态各有意思): 则为:

1
lists=DataSupport.where("state=?","3").find(OrderInfoLitePal.class);

如果是未签收(state不等于3则为未签收),则为:

1
lists=DataSupport.where("state!=?","3").find(OrderInfoLitePal.class);

3个页面非业务功能一致,所有这儿只说所有订单页面。

3. 使用 EventBus 进行通信:

因为我们的结构是下面这样的:

这里写图片描述

toolbar 位于 MainActivity 上,如果点击 toolbar ,需要 AllOrderFragment做出动作,很明显我们需要使用 EventBus,并且还需要定制我们的 Event。

如果在上图中加上 EventBus ,则变成这样:

这里写图片描述

在 AllOrder 中我们接受一个 OrderEvent,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class OrderEvent extends Event {
public int position;
public String from;
public boolean needclose;
public boolean needselect;
public OrderEvent(boolean needclose,boolean needselect, String from, int position, Object source) {
super(source);
this.needclose=needclose;
this.needselect=needselect;
this.from=from;
this.position=position;
}

position 表示是哪个 fragment 。
from 代表这条消息来自哪儿。
needclose 代表 Recycleview 的 Item 右移动画是否需要关闭。
needselet 代表 checkbox 是否需要被选择。

在 OnEvent 方法中 总共接受4种类型的消息

  • 第一种消息来自 MainActivity,代表点击了 toolbar 上面的 编辑/取消 按钮。

  • 第二种消息来自 FragmentSecond,表示当切换 fragment 时,关闭上个 fragment 的右移动画 ,并且置select 字段 false。

  • 第三种消息来自 MainActivity,表示点击了 全选/取消全选 按钮,并且置 select 字段 true/false。

  • 第四种消息来自 MainActivity,表示因为底部 fragment 切换需要关闭 item 的右移动画,并且置 select 字段false;

比如说,我们点击了全选按钮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
case R.id.toolbar_sub2title:
if ("全选".equals(getSub2Title().getText().toString())) {
getSub2Title().setText("取消全选");
// To:FragmentOrder_***.onEvent()
// false:不需要关闭动画
// true: 需要选择
// MainActivity_Allselect: 发送自 MainActivity
// 参数-1无意义
EventBusUtil.postSync(new OrderEvent(false,true,"MainActivity_Allselect", -1, this));
}
else if ("取消全选".equals(getSub2Title().getText().toString())) {
getSub2Title().setText("全选");
// To:FragmentOrder_Signed.onEvent()
EventBusUtil.postSync(new OrderEvent(false,false, "MainActivity_Allselect", -1, this));
}
break;

当然,我们在 viewpage 切换的时候需要关闭上个 fragment 的右移动画和checkbox 的被选效果,则这样就可以:

1
2
3
4
5
6
7
8
9
10
@Override
public void onPageSelected(int position) {
lastViewPageIndex=viewPagePosition;
viewPagePosition=position;
// To:FragmentOrder_Signed.onEvent() 切换,则恢复动画
EventBusUtil.postSync(new OrderEvent(true,false,"FragmentSecond",lastViewPageIndex,this));
// To:MainActivity.onEvent() 切换,则还原编辑字样
EventBusUtil.postSync(new OrderEvent(true,false,"FragmentSecond",-1,this));
}

position 发挥作用,表示上个 fragment 的位置,以明确到底是哪个 fragment接收。

4. 最后看一下单选、多选、全选,以及删除是怎么做的:

  • 单选
1
list.get(response.position).setIsselect(response.checked);

设置 isselect 字段为 true 即可;

  • 多选、全选同理。

  • 删除

最后删除的时候,我们需要遍历 list,找到 isselect字段为 true 的数据,从list 中移除,且调用 notifyItemRemoved 方法更新列表视图,最后从数据库中根据我们 model 中的 id 字段删除这条数据即可。

1
2
3
4
5
6
7
8
9
10
11
@OnClick(R.id.btn_orderall_delete)
public void onClick() {
for (int i = 0; i < list.size(); i++) {
if (list.get(i).isselect()) {
int id = list.get(i).getId();
list.remove(i);
madapter.notifyItemRemoved(i);
DataSupport.delete(OrderInfoLitePal.class, id);
}
}
}

这篇就到这儿了,代码在 github 上。


下一篇,我们来完成个人中心模块,也许包括:

  • 侧滑抽屉布局
  • 第三方登录
  • 第三方分享

我的QQ: 1003077897
我的csdn:http://blog.csdn.net/u012534831
我的github:https://github.com/qht1003077897/AppFrame
我的个人博客:https://qht1003077897.github.io

欢迎交流。