Android仿外卖购物车的实现

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

效果图

知识点分析

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

- 列表标题悬停

- 左右列表滑动时联动

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

- 底部弹出购物车清单

- 数据的同步

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

布局很简单直接进入代码

列表标题悬停

现在做项目列表什么的基本抛弃了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‘
  • xml文件中使用StickyListHeadersListView代替ListView
    <se.emilsjolander.stickylistheaders.StickyListHeadersListView
        android:layout_width="match_parent"
        android:background="#fff"
        android:id="@+id/itemListView"
        android:layout_height="match_parent">
    </se.emilsjolander.stickylistheaders.StickyListHeadersListView>
  • Adapter继承BaseAdapter和接口StickyListHeadersAdapter

StickyListHeadersAdapter接口包括两个方法

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

    long getHeaderId(int position);

代码中使用和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();

左右列表联动

联动主要有两个效果

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

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

第一个效果简单,左侧列表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));
                }
            }
        });

添加商品的动画

添加商品一共有三个动画

- 当商品从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);
    }

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

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

实现过程

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

    int[] loc = new int[2];
    v.getLocationInWindow(loc);
    activity.playAnimation(loc);

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);
    }

底部弹出购物车清单

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

bottomsheet

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

  • 集成
    compile ‘com.flipboard:bottomsheet-core:1.5.1‘
  • 使用

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

<com.flipboard.bottomsheet.BottomSheetLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/bottomSheetLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:layout_width="100dp"
            android:id="@+id/typeRecyclerView"
            android:layout_height="match_parent">
        </android.support.v7.widget.RecyclerView>

        <se.emilsjolander.stickylistheaders.StickyListHeadersListView
            android:layout_width="match_parent"
            android:background="#fff"
            android:id="@+id/itemListView"
            android:layout_height="match_parent">
        </se.emilsjolander.stickylistheaders.StickyListHeadersListView>
    </LinearLayout>
</com.flipboard.bottomsheet.BottomSheetLayout>

代码中使用很简单

    //弹出View  bottomSheet即是要弹出的view
    bottomSheetLayout.showWithSheetView(bottomSheet);

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

数据的同步

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

    //商品列表
    private ArrayList<GoodsItem> dataList;
    //分类列表
    private ArrayList<GoodsItem> typeList;
    //已选择的商品
    private SparseArray<GoodsItem> selectedList;
    //用于记录每个分组选择的数目
    private SparseIntArray groupSelect;

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);
    }

具体逻辑还是看代码吧,也许有更简单的实现。。。

Demo下载地址,下载到的文件是个AS module,你可以在自己新建的工程中Import Module.

http://download.csdn.net/detail/w804518214/9539602

时间: 2024-10-16 17:48:03

Android仿外卖购物车的实现的相关文章

Android仿外卖购物车

效果图 知识点分析 效果图来看不复杂内容并没多少,值得介绍一下的知识点也就下面几个吧 - 列表标题悬停 - 左右列表滑动时联动 - 添加商品时的抛物线动画 - 底部弹出购物车清单 - 数据的同步 另外就是实现效果的时候可能会遇到的几个坑... 布局很简单直接进入代码 列表标题悬停 现在做项目列表什么的基本抛弃了ListView改用RecyclerView,上篇博客中的标题悬停也是使用了一个RecyclerView的开源项目sticky-headers-recyclerview,不过写这个demo

Android仿京东首页轮播文字(又名垂直跑马灯)

Android仿京东首页轮播文字(又名垂直跑马灯) 京东客户端的轮播文字效果: 本次要实现的只是后面滚动的文字(前面的用ImageView或者TextView实现即可),看一下实现的效果 实现思路 上图只是一个大概的思路,要实现还需要完善更多的细节,下面会一步步的来实现这个效果: 1.封装数据源:从图上可以看到,轮播的文字是分为两个部分的,暂且把它们分别叫做前缀和内容,而且实际的使用过程中点击轮播图肯定是需要跳转页面的,而且大部分应该是WebView,不妨我们就设置点击时候需要获取的内容就是一个

【Android 仿微信通讯录 导航分组列表-上】使用ItemDecoration为RecyclerView打造带悬停头部的分组列表

[Android 仿微信通讯录 导航分组列表-上]使用ItemDecoration为RecyclerView打造带悬停头部的分组列表 一 概述 本文是Android导航分组列表系列上,因时间和篇幅原因分上下,最终上下合璧,完整版效果如下: 上部残卷效果如下:两个ItemDecoration,一个实现悬停头部分组列表功能,一个实现分割线(官方demo) 网上关于实现带悬停分组头部的列表的方法有很多,像我看过有主席的自定义ExpandListView实现的,也看过有人用一个额外的父布局里面套 Rec

Android仿微信UI布局视图(圆角布局的实现)

圆角按钮,或布局可以在xml文件中实现,但也可以使用图片直接达到所需的效果,以前版本的微信就使用了这种方法. 实现效果图:    不得不说,这种做法还是比较方便的. 源代码: MainActivity(没写任何代码,效果全在布局文件中实现): package com.android_settings; import android.app.Activity; import android.os.Bundle; public class MainActivity extends Activity

Android仿IOS回弹效果 ScrollView回弹 总结

Android仿IOS回弹效果  ScrollView回弹 总结 应项目中的需求  需要仿IOS 下拉回弹的效果 , 我在网上搜了很多 大多数都是拿scrollview 改吧改吧 试了一些  发现总有点小问题 下面的代码是我对大家发布的做了点小修改   觉得没太大问题 package com.example.myscrollview; import android.content.Context; import android.graphics.Rect; import android.util

Android仿小米商城底部导航栏之二(BottomNavigationBar、ViewPager和Fragment的联动使用)

简介 在前文<Android仿小米商城底部导航栏(基于BottomNavigationBar)>我们使用BottomNavigationBar控件模仿实现了小米商城底部导航栏效果.接下来更进一步的,我们将通过BottomNavigationBar控件和ViewPager空间的联动使用来实现主界面的滑动导航. 导航是移动应用最重要的方面之一,对用户体验是良好还是糟糕起着至关重要的作用.好的导航可以让一款应用更加易用并且让用户快速上手.相反,糟糕的应用导航很容易让人讨厌,并遭到用户的抛弃.为了打造

Android仿微信下拉列表实现

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 本文要实现微信6.1中点击顶部菜单栏的"+"号按钮时,会弹出一个列表框.这里用的了Activity实现,其实最好的方法可以用ActionBar,不过这货好像只支持3.0以后的版本.本文的接上文Android仿微信底部菜单栏+顶部菜单栏(附源码) 效果: 一.仿微信下拉列表布局pop_dialog.xml <?xml version="1.0" encodi

android 仿小米icon处理,加阴影和边框

本人自己在做一个launcher,所以需要处理icon,加阴影和边框等.这只是一种处理方法,其他的处理方法类似. 源代码: https://github.com/com314159/LauncherIconMaskEffect 效果图: 核心思想: 1. 绘制边框时,先取原图片的颜色,再根据颜色绘制边框,加上了渐变效果 2.添加阴影其实非常简单,只是加了一张背景图片 android 仿小米icon处理,加阴影和边框,布布扣,bubuko.com

android 仿ppt进入动画效果合集

EnterAnimation android 仿ppt进入动画效果合集, 百叶窗效果,擦除效果,盒状效果,阶梯效果,菱形效果,轮子效果,劈裂效果,棋盘效果, 切入效果,扇形展开效果,十字扩展效果,随机线条效果,向内溶解效果,圆形扩展效果, 适用于各种view和viewgroup,activity即用于页面根部viewgroup, 自定义viewgroup自动换行layout, 看效果图 Series of entrance animation effects just like ppt in A