Android仿外賣購物車功能
本文實例為大家分享了Android實現(xiàn)外賣購物車功能的具體代碼,供大家參考,具體內(nèi)容如下
先看看效果圖:
知識點分析
效果圖來看不復(fù)雜內(nèi)容并沒多少,值得介紹一下的知識點也就下面幾個吧
- 列表標(biāo)題懸停
- 左右列表滑動時聯(lián)動
- 添加商品時的拋物線動畫
- 底部彈出購物車清單
- 數(shù)據(jù)的同步
另外就是實現(xiàn)效果的時候可能會遇到的幾個坑。。。
布局很簡單直接進(jìn)入代碼
1:列表標(biāo)題懸停
現(xiàn)在做項目列表什么的基本拋棄了ListView改用RecyclerView,上篇博客中的標(biāo)題懸停也是使用了一個RecyclerView的開源項目sticky-headers-recyclerview,不過寫這個demo的時候遇到了兩個坑
1)、sticky-headers-recyclerview做懸停標(biāo)題的時候scrollToPosition(int position)方法滾動的位置不準(zhǔn)確。
2)、當(dāng)布局復(fù)雜點的時候 如果RecyclerView的寬度自適應(yīng)或者使用權(quán)重百分比之類可能會導(dǎo)致header顯示空白。
并且該開源項目作者已經(jīng)停止維護(hù),所以這次又換回了StickyListHeadersListView。
需要購物車Demo的很多都是新手,這里簡單介紹下StickyListHeadersListView的使用
1)、AS引用 gradle文件dependencies內(nèi)添加
compile 'se.emilsjolander:stickylistheaders:2.7.0'
2)、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>
3)、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();
2:左右列表聯(lián)動
聯(lián)動主要有兩個效果
- 左側(cè)列表點擊選擇分類,右側(cè)列表滑動到對應(yīng)分類
- 右側(cè)列表滑動過程中左側(cè)列表高亮的分類跟隨變化
第一個效果簡單,左側(cè)列表item添加點擊事件,事件中調(diào)用右側(cè)列表的setSelection(int positon) 方法。
第二個效果要給右側(cè)列表添加ScrollListener,根據(jù)列表中顯示的第一條數(shù)據(jù)設(shè)置左側(cè)選中的分類
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) { //根據(jù)firstVisibleItem獲取分類ID,根據(jù)分類id獲取左側(cè)要選中的位置 GoodsItem item = dataList.get(firstVisibleItem); if(typeAdapter.selectTypeId != item.typeId) { typeAdapter.selectTypeId = item.typeId; typeAdapter.notifyDataSetChanged(); //左側(cè)列表是個RecyclerView 所以使用smoothScrollToPosition(int position) 使當(dāng)對應(yīng)position的item可以滾動顯示出來 rvType.smoothScrollToPosition(int position)(getSelectedGroupPosition(item.typeId)); } } });
3:添加商品的動畫
添加商品一共有三個動畫
- 當(dāng)商品從0到1 旋轉(zhuǎn)左移顯示出減號按鈕
- 當(dāng)商品從1到0 減號按鈕旋轉(zhuǎn)右移消失
- 添加商品時拋物線動畫添加到購物車圖標(biāo)
前兩個動畫很簡單可以分解成三個補(bǔ)間動畫 旋轉(zhuǎn)、平移、透明度。
可以用xml完成,也可以代碼設(shè)置,不過有個小坑要注意一下 旋轉(zhuǎn)動畫一定要在平移動畫前面,否則就不是滾動平移了,而是亂跳。。。
這里貼一下動畫的代碼設(shè)置方法
//顯示減號的動畫 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; } //執(zhí)行動畫 只需給對應(yīng)控件setAnimation然后調(diào)用setVisibility方法即可 { .... tvMinus.setAnimation(getHiddenAnimation()); tvMinus.setVisibility(View.GONE); }
拋物線動畫和上面的差不多可以分解成兩個平移動畫,不過兩個平移動畫的差值器一個線性一個加速而已,因為動畫界面跨度比較大所以需要在根部局內(nèi)寫,不能寫在列表的item中(這樣會顯示不全)。
代碼中的anim_mask_layout 即為整個布局文件的根布局,這里是一個RelativeLayout
實現(xiàn)過程:
1、首先點擊加號圖標(biāo),拿到控件在屏幕上的絕對坐標(biāo),回調(diào)activity顯示動畫
int[] loc = new int[2]; v.getLocationInWindow(loc); activity.playAnimation(loc);
2、創(chuàng)建動畫的控件并添加到根部局并在動畫結(jié)束后移除動畫view
public void playAnimation(int[] start_location){ ImageView img = new ImageView(this); img.setImageResource(R.drawable.button_add); setAnim(img,start_location); } //創(chuàng)建動畫 平移動畫直接傳遞偏移量 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); //線性插值器 默認(rèn)就是線性 translationX.setInterpolator(new LinearInterpolator()); Animation translationY = new TranslateAnimation(0, 0, 0, des[1]-startY); //設(shè)置加速插值器 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在根部局中的坐標(biāo) 添加到根部局中 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); } //設(shè)置動畫結(jié)束移除動畫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); }
4:底部彈出購物車清單
底部彈出的效果大家一定都很熟悉了,幾回每個項目中都會用的到,官方?jīng)]有提供簡單的控件實現(xiàn),一般都需要自己寫,不過要做到簡單流暢,便于移植推薦使用第三方庫,這里向大家推薦一個
集成簡單,效果多樣這里簡單介紹一下使用方法
集成
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();
5:數(shù)據(jù)的同步
同步數(shù)據(jù),控制界面刷新應(yīng)該是新手最容易繞彎的地方了,其實只要仔細(xì)一點也不難,這里簡單提供一種思路(并不一定適合你的項目).
//商品列表 private ArrayList<GoodsItem> dataList; //分類列表 private ArrayList<GoodsItem> typeList; //已選擇的商品 private SparseArray<GoodsItem> selectedList; //用于記錄每個分組選擇的數(shù)目 private SparseIntArray groupSelect;
SparseArray這個類其實就是 HashMap< Integer,Object >
不過SparseArray既可以根據(jù)key查找Value,也可以根據(jù)位置查找value,性能比HashMap高,是官方推薦的替代類,
同樣SparseIntArray 其實是HashMap< Integer,Integer> 的替代者。
Activity里實現(xiàn)了下面幾個方法,用于數(shù)據(jù)統(tǒng)一管理
列表中顯示的商品購買數(shù)量統(tǒng)一從activity獲取,商品的加減統(tǒng)一調(diào)用Activity的方法然后notifiDatasetChanged,由于代碼不少具體的還是看源碼吧
/** * Item代表商品的購買數(shù)量加一 * @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商品的購買數(shù)量減一 * @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); } /** * 刷新界面 總價、購買數(shù)量等 * @param refreshGoodList 是否刷新商品list */ private void update(boolean refreshGoodList){ ... } //根據(jù)商品id獲取當(dāng)前商品的采購數(shù)量 public int getSelectedItemCountById(int id){ GoodsItem temp = selectedList.get(id); if(temp==null){ return 0; } return temp.count; } //根據(jù)類別Id獲取屬于當(dāng)前類別的數(shù)量 public int getSelectedGroupCountByTypeId(int typeId){ return groupSelect.get(typeId); }
具體邏輯還是看代碼吧,也許有更簡單的實現(xiàn)。。。
Demo下載地址,下載到的文件是個AS module,你可以在自己新建的工程中Import Module.
源碼下載:Android仿外賣購物車功能
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android中AndroidStudio&Kotlin安裝到運行過程及常見問題匯總
這篇文章主要介紹了Android(AndroidStudio&Kotlin)安裝到運行過程及常見問題匯總,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒借鑒價值,需要的朋友可以參考下2020-03-03Android中Glide加載圖片并實現(xiàn)圖片緩存
本篇文章主要介紹了Android中Glide加載圖片并實現(xiàn)圖片緩存,這里和大家簡單的分享一下Glide的使用方法以及緩存 ,有興趣的可以了解一下。2017-03-03Android webveiw 出現(xiàn)棧錯誤解決辦法
這篇文章主要介紹了Android webveiw 出現(xiàn)棧錯誤解決辦法的相關(guān)資料,出現(xiàn)java.lang.UnsupportedOperationException: For security reasons, WebView is not allowed in privileged processes,這里提供解決辦法,需要的朋友可以參考下2017-08-08Jetpack?Compose重寫TopAppBar實現(xiàn)標(biāo)題多行折疊詳解
這篇文章主要為大家介紹了Jetpack?Compose重寫TopAppBar實現(xiàn)標(biāo)題多行折疊示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Android基于廣播事件機(jī)制實現(xiàn)簡單定時提醒功能代碼
這篇文章主要介紹了Android基于廣播事件機(jī)制實現(xiàn)簡單定時提醒功能代碼,較為詳細(xì)的分析了Android廣播事件機(jī)制及提醒功能的相關(guān)實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10