android外卖购物车,Android仿外卖购物车的实现

又两周没写博客了,不是不想写而是不知道该写点什么,总不能为了写博客而写博客,前两天项目里要加个购物车功能,看了下别人APP的效果觉得不错,虽然我项目里没用上不过毕竟还算是个常用的功能,于是决定写个博客分享下!

效果图

73255833_3

知识点分析

效果图来看不复杂内容并没多少,值得介绍一下的知识点也就下面几个吧

- 列表标题悬停

- 左右列表滑动时联动

- 添加商品时的抛物线动画

- 底部弹出购物车清单

- 数据的同步

另外就是实现效果的时候可能会遇到的几个坑。。。

布局很简单直接进入代码

列表标题悬停

现在做项目列表什么的基本抛弃了ListView改用RecyclerView,上篇博客中的标题悬停也是使用了一个RecyclerView的开源项目sticky-headers-recyclerview,不过写这个demo的时候遇到了两个坑

sticky-headers-recyclerview做悬停标题的时候scrollToPosition(int position)方法滚动的位置不准确。

当布局复杂点的时候 如果RecyclerView的宽度自适应或者使用权重百分比之类可能会导致header显示空白。

并且该开源项目作者已经停止维护,所以这次又换回了StickyListHeadersListView。

需要购物车Demo的很多都是新手,这里简单介绍下StickyListHeadersListView的使用

AS引用 gradle文件dependencies内添加

compile 'se.emilsjolander:stickylistheaders:2.7.0'11

xml文件中使用StickyListHeadersListView代替ListView

android:layout_width="match_parent"

android:background="#fff"

android:id="@+id/itemListView"

android:layout_height="match_parent">

1

2

3

4

5

61

2

3

4

5

6

Adapter继承BaseAdapter和接口StickyListHeadersAdapter

StickyListHeadersAdapter接口包括两个方法

View getHeaderView(int position, View convertView, ViewGroup parent);

long getHeaderId(int position);1

2

3

41

2

3

4

代码中使用和ListView一样,下面是几个特有的方法,看方法名也很容易理解用途

public void setAreHeadersSticky(boolean areHeadersSticky);

public boolean areHeadersSticky();

public void setOnHeaderClickListener(OnHeaderClickListener listener);

public interface OnHeaderClickListener {

public void onHeaderClick(StickyListHeadersListView l, View header, int itemPosition, long headerId, boolean currentlySticky);

}

public void setOnStickyHeaderChangedListener(OnStickyHeaderChangedListener listener);

public interface OnStickyHeaderChangedListener {

void onStickyHeaderChanged(StickyListHeadersListView l, View header, int itemPosition, long headerId);

}

public View getListChildAt(int index);

public int getListChildCount();1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

171

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

左右列表联动

联动主要有两个效果

- 左侧列表点击选择分类,右侧列表滑动到对应分类

- 右侧列表滑动过程中左侧列表高亮的分类跟随变化

第一个效果简单,左侧列表item添加点击事件,事件中调用右侧列表的setSelection(int positon) 方法。

第二个效果要给右侧列表添加ScrollListener,根据列表中显示的第一条数据设置左侧选中的分类

listView.setOnScrollListener(new AbsListView.OnScrollListener() {

@Override

public void onScrollStateChanged(AbsListView view, int scrollState) {

}

@Override

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

//根据firstVisibleItem获取分类ID,根据分类id获取左侧要选中的位置

GoodsItem item = dataList.get(firstVisibleItem);

if(typeAdapter.selectTypeId != item.typeId) {

typeAdapter.selectTypeId = item.typeId;

typeAdapter.notifyDataSetChanged();

//左侧列表是个RecyclerView 所以使用smoothScrollToPosition(int position) 使当对应position的item可以滚动显示出来

rvType.smoothScrollToPosition(int position)(getSelectedGroupPosition(item.typeId));

}

}

});1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

181

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

添加商品的动画

添加商品一共有三个动画

- 当商品从0到1 旋转左移显示出减号按钮

- 当商品从1到0 减号按钮旋转右移消失

- 添加商品时抛物线动画添加到购物车图标

前两个动画很简单可以分解成三个补间动画 旋转、平移、透明度。

可以用xml完成,也可以代码设置,不过有个小坑要注意一下 旋转动画一定要在平移动画前面,否则就不是滚动平移了,而是乱跳。。。

这里贴一下动画的代码设置方法

//显示减号的动画

private Animation getShowAnimation(){

AnimationSet set = new AnimationSet(true);

RotateAnimation rotate = new RotateAnimation(0,720,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);

set.addAnimation(rotate);

TranslateAnimation translate = new TranslateAnimation(

TranslateAnimation.RELATIVE_TO_SELF,2f

,TranslateAnimation.RELATIVE_TO_SELF,0

,TranslateAnimation.RELATIVE_TO_SELF,0

,TranslateAnimation.RELATIVE_TO_SELF,0);

set.addAnimation(translate);

AlphaAnimation alpha = new AlphaAnimation(0,1);

set.addAnimation(alpha);

set.setDuration(500);

return set;

}

//隐藏减号的动画

private Animation getHiddenAnimation(){

AnimationSet set = new AnimationSet(true);

RotateAnimation rotate = new RotateAnimation(0,720,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);

set.addAnimation(rotate);

TranslateAnimation translate = new TranslateAnimation(

TranslateAnimation.RELATIVE_TO_SELF,0

,TranslateAnimation.RELATIVE_TO_SELF,2f

,TranslateAnimation.RELATIVE_TO_SELF,0

,TranslateAnimation.RELATIVE_TO_SELF,0);

set.addAnimation(translate);

AlphaAnimation alpha = new AlphaAnimation(1,0);

set.addAnimation(alpha);

set.setDuration(500);

return set;

}

//执行动画 只需给对应控件setAnimation然后调用setVisibility方法即可

{

....

tvMinus.setAnimation(getHiddenAnimation());

tvMinus.setVisibility(View.GONE);

}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

391

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

抛物线动画和上面的差不多可以分解成两个平移动画,不过两个平移动画的差值器一个线性一个加速而已,因为动画界面跨度比较大所以需要在根部局内写,不能写在列表的item中(这样会显示不全)。

代码中的anim_mask_layout 即为整个布局文件的根布局,这里是一个RelativeLayout

实现过程

1、首先点击加号图标,拿到控件在屏幕上的绝对坐标,回调activity显示动画

int[] loc = new int[2];

v.getLocationInWindow(loc);

activity.playAnimation(loc);1

2

31

2

3

2、创建动画的控件并添加到根部局并在动画结束后移除动画view

public void playAnimation(int[] start_location){

ImageView img = new ImageView(this);

img.setImageResource(R.drawable.button_add);

setAnim(img,start_location);

}

//创建动画 平移动画直接传递偏移量

private Animation createAnim(int startX,int startY){

int[] des = new int[2];

imgCart.getLocationInWindow(des);

AnimationSet set = new AnimationSet(false);

Animation translationX = new TranslateAnimation(0, des[0]-startX, 0, 0);

//线性插值器 默认就是线性

translationX.setInterpolator(new LinearInterpolator());

Animation translationY = new TranslateAnimation(0, 0, 0, des[1]-startY);

//设置加速插值器

translationY.setInterpolator(new AccelerateInterpolator());

Animation alpha = new AlphaAnimation(1,0.5f);

set.addAnimation(translationX);

set.addAnimation(translationY);

set.addAnimation(alpha);

set.setDuration(500);

return set;

}

//计算动画view在根部局中的坐标 添加到根部局中

private void addViewToAnimLayout(final ViewGroup vg, final View view,

int[] location) {

int x = location[0];

int y = location[1];

int[] loc = new int[2];

vg.getLocationInWindow(loc);

view.setX(x);

view.setY(y-loc[1]);

vg.addView(view);

}

//设置动画结束移除动画view

private void setAnim(final View v, int[] start_location) {

addViewToAnimLayout(anim_mask_layout, v, start_location);

Animation set = createAnim(start_location[0],start_location[1]);

set.setAnimationListener(new Animation.AnimationListener() {

@Override

public void onAnimationStart(Animation animation) {

}

@Override

public void onAnimationEnd(final Animation animation) {

//直接remove可能会因为界面仍在绘制中成而报错

mHanlder.postDelayed(new Runnable() {

@Override

public void run() {

anim_mask_layout.removeView(v);

}

},100);

}

@Override

public void onAnimationRepeat(Animation animation) {

}

});

v.startAnimation(set);

}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

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

671

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

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

底部弹出购物车清单

底部弹出的效果大家一定都很熟悉了,几回每个项目中都会用的到,官方没有提供简单的控件实现,一般都需要自己写,不过要做到简单流畅,便于移植推荐使用第三方库,这里向大家推荐一个

集成简单,效果多样这里简单介绍一下使用方法

集成

compile 'com.flipboard:bottomsheet-core:1.5.1'11

使用

xml中使用BottomSheetLayout包裹弹出view时候的背景布局,BottomSheetLayout继承自帧布局

xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/bottomSheetLayout"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:orientation="horizontal"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:layout_width="100dp"

android:id="@+id/typeRecyclerView"

android:layout_height="match_parent">

android:layout_width="match_parent"

android:background="#fff"

android:id="@+id/itemListView"

android:layout_height="match_parent">

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

241

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

代码中使用很简单

//弹出View bottomSheet即是要弹出的view

bottomSheetLayout.showWithSheetView(bottomSheet);

//代码隐藏view (点击弹出view以外的地方可以隐藏弹出的view,向下滑动也可以)

bottomSheetLayout.dismissSheet();1

2

3

4

5

61

2

3

4

5

6

数据的同步

同步数据,控制界面刷新应该是新手最容易绕弯的地方了,其实只要仔细一点也不难,这里简单提供一种思路(并不一定适合你的项目).

//商品列表

private ArrayList dataList;

//分类列表

private ArrayList typeList;

//已选择的商品

private SparseArray selectedList;

//用于记录每个分组选择的数目

private SparseIntArray groupSelect;1

2

3

4

5

6

7

81

2

3

4

5

6

7

8

SparseArray这个类其实就是 HashMap< Integer,Object >

不过SparseArray既可以根据key查找Value,也可以根据位置查找value,性能比HashMap高,是官方推荐的替代类,

同样SparseIntArray 其实是HashMap< Integer,Integer> 的替代者。

Activity里实现了下面几个方法,用于数据统一管理

列表中显示的商品购买数量统一从activity获取,商品的加减统一调用Activity的方法然后notifiDatasetChanged,由于代码不少具体的还是看源码吧

/**

* Item代表商品的购买数量加一

*@param item

*@param refreshGoodList 是否刷新商品list

*/

public void add(GoodsItem item,boolean refreshGoodList){

int groupCount = groupSelect.get(item.typeId);

if(groupCount==0){

groupSelect.append(item.typeId,1);

}else{

groupSelect.append(item.typeId,++groupCount);

}

GoodsItem temp = selectedList.get(item.id);

if(temp==null){

item.count=1;

selectedList.append(item.id,item);

}else{

temp.count++;

}

update(refreshGoodList);

}

/**

* Item商品的购买数量减一

*@param item

*@param refreshGoodList 是否刷新商品list

*/

public void remove(GoodsItem item,boolean refreshGoodList){

int groupCount = groupSelect.get(item.typeId);

if(groupCount==1){

groupSelect.delete(item.typeId);

}else if(groupCount>1){

groupSelect.append(item.typeId,--groupCount);

}

GoodsItem temp = selectedList.get(item.id);

if(temp!=null){

if(temp.count<2){

selectedList.remove(item.id);

}else{

item.count--;

}

}

update(refreshGoodList);

}

/**

* 刷新界面 总价、购买数量等

*@param refreshGoodList 是否刷新商品list

*/

private void update(boolean refreshGoodList){

...

}

//根据商品id获取当前商品的采购数量

public int getSelectedItemCountById(int id){

GoodsItem temp = selectedList.get(id);

if(temp==null){

return 0;

}

return temp.count;

}

//根据类别Id获取属于当前类别的数量

public int getSelectedGroupCountByTypeId(int typeId){

return groupSelect.get(typeId);

}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

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

681

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值