最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android仿eleme點餐頁面二級聯(lián)動列表

 更新時間:2016年10月19日 09:36:15   作者:梁山boy  
本站一直在點外賣,于是心血來潮就像仿餓了么做個站,接下來通過本文給大家介紹android 二級聯(lián)動列表,仿eleme點餐頁面的相關資料,需要的朋友可以參考下

本周末外賣點得多,就仿一仿“餓了么”好了。先上圖吧,這樣的訂單頁面是不是很眼熟:

這里寫圖片描述

右邊的listview分好組以后,在左邊的Tab頁建立索引??梢灾苯訉Ш剑遣皇呛芊奖恪jP鍵在于右邊滑動,左邊也會跟著滑;而點擊左邊呢,也能定位右邊的項。它們存在這樣一種特殊的交互。像這種聯(lián)動的效果,還有些常見的例子呢,比如知乎采用了常見的toolbar+viewPager的聯(lián)動,只不過是上下布局:

這里寫圖片描述

再看看點評,它的城市選擇頁面也有這種聯(lián)動的影子,只是稍微弱一點。側(cè)邊欄可以對listview進行索引,這最早是在微信好友列表里出現(xiàn)的把:

這里寫圖片描述

趁著周末,我也擼一個。就拓展性而言,應該可以適配以上所有情況吧。我稱其為LinkedLayout,看下效果圖:

這里寫圖片描述

我把右邊按5個一組,可以看到,左邊的索引 = 右邊/5

特點

右邊滑動,左邊跟著動

左邊滑動到邊界,右邊跟著動

點擊左邊tab項,右邊滑動定位到相應的group

源碼

github 傳送門: https://github.com/fashare2015/LinkedScrollDemo

知識點

做之前先羅列一下知識點,或者說我們能從這個demo里收獲到什么。

面向抽象/接口編程

自定義 view

代理模式

UML類圖

復習 listview && recyclerview 的細節(jié)

感覺做完以后收獲最大的還是第一點,面向接口編程。事實上,完成功能的時間只占了一半,后邊的時間一直在抽象和重構;哎,一步到位太難了,還是老老實實寫具體類,再抽取基類把。

構思

UI部分

LinkedLayout

要做的呢是兩個相互關聯(lián)的列表,在左邊的作為tab頁,右邊的作為content頁。先不考慮交互,我們來打個界面:搞一個叫做LinkedLayout的類,用來盛放tab和content:

這里寫圖片描述

public class LinkedLayout extends LinearLayout {
  private Context mContext;
  private BaseScrollableContainer mTabContainer;
  private BaseScrollableContainer mContentContainer;
  private SectionIndexer mSectionIndexer; // 代理
  ...
}

我們讓它繼承了LinearLayout,同時持有兩個Container的東東,還有一個上帝對象mContext,以及一個分組用的SectionIndexer。

BaseScrollableContainer

先別管這些,主要看兩個Container,從名字上看一個是tab頁,一個是content頁,嘿嘿。因為它們都能scroll嘛,干脆搞一個BaseScrollableContainer把。取名為Container呢,當然是致敬Fragment啦。我們來定義一下這個類:
初步一想,無非有一個 mContext, 一個 viewGroup, 還有一些 Listener 嘛:

這里寫圖片描述

public abstract class BaseScrollableContainer<VG extends ViewGroup> {
  protected Context mContext;
  public VG mViewGroup;
  protected RealOnScrollListener mRealOnScrollListener;
  private EventDispatcher mEventDispatcher;
  ...
}

和我們預想的差不多嘛,mContext上下文,mViewGroup基本就是指代我們的兩個listview了吧。當然,我之后可是要做toolbar+viewpager的,肯定得依賴抽象,不能直接寫listview啦。余下兩個是Listener,等我們界面搭好,寫交互的時候在看把。

看來UML圖還是有好處的,繼承和依賴關系一目了然。

自定義View && 動態(tài)布局

好了到了自定義view地環(huán)節(jié)了。我們已經(jīng)有了一個LinkedLayout,這是我們的activity_main.xml布局代碼:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <com.fashare.linkedscrolldemo.ui.LinkedLayout
    android:id="@+id/linked_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"/>
</RelativeLayout>

擦,就沒了嘛?剩下的得靠Java代碼來搞啦?;氐絃inkedLayout咱們來布局UI~:

public class LinkedLayout extends LinearLayout {
  ...
  private static final int MEASURE_BY_WEIGHT = 0;
  private static final float WEIGHT_TAB = 1;
  private static final float WEIGHT_CONTENT = 3;

  public void setContainers(BaseScrollableContainer tabContainer, BaseScrollableContainer contentContainer) {
    mTabContainer = tabContainer;
    mContentContainer = contentContainer;
    mTabContainer.setEventDispatcher(this);
    mContentContainer.setEventDispatcher(this);

    // 設置 LayoutParams
    mTabContainer.mViewGroup.setLayoutParams(new LinearLayout.LayoutParams(
        MEASURE_BY_WEIGHT,
        ViewGroup.LayoutParams.WRAP_CONTENT,
        WEIGHT_TAB
    ));

    mContentContainer.mViewGroup.setLayoutParams(new LinearLayout.LayoutParams(
        MEASURE_BY_WEIGHT,
        ViewGroup.LayoutParams.MATCH_PARENT,
        WEIGHT_CONTENT
    ));

    this.addView(mTabContainer.mViewGroup);
    this.addView(mContentContainer.mViewGroup);
    this.setOrientation(HORIZONTAL);
  }
}

搞了個setContainers用來注入我們的Container,里邊有一些像layout_height,layout_width,layout_weight,orientation之類的,很眼熟吧,和xml沒差。順便一提的是,我們用了weight屬性來控制這個比例1:3,一直感覺這個屬性比較神奇。。。

注入ViewGroup, 使用自定義的LinkedLayout

到這里為止,LinkedLayout已經(jīng)布局好了,我們分別注入ViewGroup就可以用了。我這里分別用listview作tab,recyclerview作content。想像力有限,用來用去好像也就這么幾個控件。。。這部分代碼很簡單,在MainActivity里,就不貼了。

子類化 BaseScrollableContainer

按照常理,下邊應該實現(xiàn)基類了吧。前面的MainActivity中,我們是這樣實例化的:

mTabContainer = new ListViewTabContainer(this, mListView); 
mContentContainer = new RecyclerViewContentContainer(this, mRecyclerView);

看名字一個是listview填充的tab,一個是recyclerview填充的content。就先實現(xiàn)這兩個類吧,從圖中可以看到,它們分別繼承于BaseScrollableContainer,并被LinkedLayout所持有:

這里寫圖片描述 

交互部分

與用戶的交互:OnScrollListener 與 代理模式

終于到了交互部分,既然是滑動,那少不了定義監(jiān)聽器啦。然而,麻煩在于listview和recyclerview各自的OnScrollListener還不一樣,這個時候如果各自實現(xiàn)的話,既麻煩,又有冗余。像這樣子:

// RecyclerView
public class RecyclerViewContentContainer extends BaseScrollableContainer<RecyclerView> {
  ...
  @Override
  protected void setOnScrollListener() {
    mViewGroup.addOnScrollListener(new ProxyOnScrollListener());
  }

  private class ProxyOnScrollListener extends RecyclerView.OnScrollListener {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
      if(newState == RecyclerView.SCROLL_STATE_IDLE) {      // 停止滑動
        1.停止時的邏輯...
      }else if(newState == RecyclerView.SCROLL_STATE_DRAGGING){  // 按下拖動
        2.剛剛拖動時的邏輯...
      }
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) { // 滑動
      3.滑動時的邏輯...
    }
  }
}

// ListView
public class ListViewTabContainer extends BaseScrollableContainer<ListView> {
  ...
  @Override
  protected void setOnScrollListener() {
    mViewGroup.setOnScrollListener(new ProxyOnScrollListener());
    ...
  }

  public class ProxyOnScrollListener implements AbsListView.OnScrollListener{
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
      if(scrollState == SCROLL_STATE_IDLE) {       // 停止滑動
        1.停止時的邏輯...
      }else if(scrollState == SCROLL_STATE_TOUCH_SCROLL) // 按下拖動
        2.剛剛拖動時的邏輯...
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
      3.滑動時的邏輯...        // 滑動
    }
  }
}

那該怎么辦呢,雖然各自的OnScrollListener差異挺大,但是仔細觀察可以發(fā)現(xiàn)其實很多邏輯都是類似的,可以共用的。這時恰恰可以用代理模式來做重構。我抽取了1、2、3處的邏輯,由于在抽象意義上是一致的,可以整理成接口:

public interface OnScrollListener {
  // tab 點擊事件
  void onClick(int position);

  // 1.滑動開始
  void onScrollStart();

  // 2.滑動結(jié)束
  void onScrollStop();

  // 3.觸發(fā) onScrolled()
  void onScrolled();

  // 用戶手動滑, 觸發(fā)的 onScrolled()
  void onScrolledByUser();

  // 程序調(diào)用 scrollTo(), 觸發(fā)的 onScrolled()
  void onScrolledByInvoked();
}

與此同時,RecyclerView和ListView各自的監(jiān)聽器便分別作為代理類,把1、2、3的邏輯都委托給某個接盤俠,不必自己去實現(xiàn),倒也落的輕松自在。如圖所示:這里寫圖片描述

然后,讓我們來看看這個接盤俠:RealOnScrollListener。。。

不愧是一個老實類,它老實地接盤了OnScrollListener的所有接口,并被兩個代理類Proxy…所持有(圖中并未畫出。。)。
具體實現(xiàn)就不貼了,大家可以下源碼來看。這里大致分析一下,它有三個成員:

public class RealOnScrollListener implements OnScrollListener {
  public boolean isTouching = false; // 處于觸摸狀態(tài)
  private int mCurPosition = 0;    // 當前選中項
  private BaseViewGroupUtil<VG> mViewUtil; // ViewGroup 工具類
  ...
}

isTouching:

為啥要維護這個觸摸狀態(tài)呢?這是由于我們的效果是聯(lián)動的。這就比較討厭了,當onScrolled()被調(diào)用,我們分不清是用戶的滑動,還是來自另一個列表滑動時的聯(lián)動效果。那我們記錄一下isTouching狀態(tài)呢,就能區(qū)分開這兩種情況了。
更改isTouching的邏輯在onScrollStart()和onScrollStop()里邊。

mCurPosition:

這個很好解釋,我們每次滑動需要記錄當前位置,然后通知另一個列表進行聯(lián)動。
這段邏輯在onScrolled()里邊。

mViewUtil:
一個工具庫,用于簡化邏輯。大概有scrollTo(),setViewSelected(),UpdatePosOnScrolled()等方法,如圖:

這里寫圖片描述 

兩個Container之間的交互

之前都是對用戶的交互,終于到聯(lián)動部分了。不急著實現(xiàn),先回答我一個問題:假設我一個Activity里持有兩個Fragment,問它們之間如何通信?

A同學大聲道:用廣播
B同學:EventBus !!!
C同學:看我 RxBus 。。。
別鬧好嗎。。。給我老老實實用Listener。顯然,我們這里面臨的是同樣的場景。LinkedLayout=Activity,Container=Fragment。
動手前先定義Listener吧,要取個中二點的名字:

/*
 * 事件分發(fā)者
 */
public interface EventDispatcher {
  /**
   * 分發(fā)事件: fromView 中的 pos 被選中
   * @param pos
   * @param fromView
   */
  void dispatchItemSelectedEvent(int pos, View fromView);
}
/*
 * 事件接受者
 */
public interface EventReceiver {
  /**
   * 收到事件: 立即選中 newPos
   * @param newPos
   */
  void selectItem(int newPos);
}

然后LinkedLayout作為父級元素,肯定是分發(fā)者的角色,應當實現(xiàn)EventDispatcher;而BaseScrollableContainer作為子元素,接受該事件,應當實現(xiàn)EventReceiver??聪骂悎D:

這里寫圖片描述

看下相應的實現(xiàn)(EventReceiver):

public abstract class BaseScrollableContainer<VG extends ViewGroup>
    implements EventReceiver {
  protected RealOnScrollListener mRealOnScrollListener;
  private EventDispatcher mEventDispatcher; // 持有分發(fā)者
  ...
  public void setEventDispatcher(EventDispatcher eventDispatcher) {
    mEventDispatcher = eventDispatcher;
  }
  // 掉用 mEventDispatcher,也就是 LinkedLayout
  protected void dispatchItemSelectedEvent(int curPosition){
    if(mEventDispatcher != null)
      mEventDispatcher.dispatchItemSelectedEvent(curPosition, mViewGroup);
  }
  @Override
  public void selectItem(int newPos) {
    mRealOnScrollListener.selectItem(newPos);
  }
  // OnScrollListener: 代理模式
  public class RealOnScrollListener implements OnScrollListener {
    ...
    public void selectItem(int position){
      mCurPosition = position;
      Log.d("setitem", position + "");
      // 來自另一邊的聯(lián)動事件
      mViewUtil.smoothScrollTo(position);
//      if(mViewUtil.isVisiblePos(position))  // curSection 可見時, 不滾動
        mViewUtil.setViewSelected(position);
    }
    @Override
    public void onClick(int position) {
      isTouching = true;
      mViewUtil.setViewSelected(mCurPosition = position);
      dispatchItemSelectedEvent(position); // 點擊tab,分發(fā)事件
      isTouching = false;
    }
    ...
    @Override
    public void onScrolled() {
      mCurPosition = mViewUtil.updatePosOnScrolled(mCurPosition);
      if(isTouching)     // 來自用戶, 通知 對方 聯(lián)動
        onScrolledByUser();
      else          // 來自對方, 被動滑動不響應
        onScrolledByInvoked();
    }
    @Override
    public void onScrolledByUser() {
      dispatchItemSelectedEvent(mCurPosition);  // 來自用戶, 通知 對方 聯(lián)動
    }
  }
}

再看(EventDispatcher):

public class LinkedLayout extends LinearLayout implements EventDispatcher {
  private BaseScrollableContainer mTabContainer;
  private BaseScrollableContainer mContentContainer;
  private SectionIndexer mSectionIndexer; // 分組接口
  ...
  @Override
  public void dispatchItemSelectedEvent(int pos, View fromView) {
    if (fromView == mContentContainer.mViewGroup) { // 來自 content, 轉(zhuǎn)發(fā)給 tab
      int convertPos = mSectionIndexer.getSectionForPosition(pos);
      mTabContainer.selectItem(convertPos);
    } else {          // 來自 tab, 轉(zhuǎn)發(fā)給 content
      int convertPos = mSectionIndexer.getPositionForSection(pos);
      mContentContainer.selectItem(convertPos);
    }
  }
}

總結(jié)

到此為止,有沒有一種酣暢淋漓的感覺?不管怎么說,面向?qū)ο笫切叛?,定義好接口以后,實現(xiàn)起來怎么寫怎么舒服。
// TODO: 之前說了,這個聯(lián)動是通用的。之后有時間會繼續(xù)實現(xiàn)一個toolbar+viewPager的聯(lián)動…

彩蛋

高清無碼類圖:(完整)

這里寫圖片描述

相關文章

  • android實現(xiàn)條目倒計時功能

    android實現(xiàn)條目倒計時功能

    這篇文章主要為大家詳細介紹了android實現(xiàn)條目倒計時功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • android第三方分享方式的簡單實現(xiàn)

    android第三方分享方式的簡單實現(xiàn)

    這篇文章主要為大家詳細介紹了android第三方分享方式的簡單實現(xiàn),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • 詳解Android冷啟動實現(xiàn)APP秒開的方法

    詳解Android冷啟動實現(xiàn)APP秒開的方法

    這篇文章給大家介紹的是Android冷啟動實現(xiàn)APP秒開的方法,對大家日常開發(fā)APP還是很實用的,有需要的可以參考借鑒。
    2016-08-08
  • Android編程中的5種數(shù)據(jù)存儲方式

    Android編程中的5種數(shù)據(jù)存儲方式

    這篇文章主要介紹了Android編程中的5種數(shù)據(jù)存儲方式,結(jié)合實例形式詳細分析了Android實現(xiàn)數(shù)據(jù)存儲的5中實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-12-12
  • Android中EditText的drawableRight屬性設置點擊事件

    Android中EditText的drawableRight屬性設置點擊事件

    這篇文章主要介紹了Android中EditText的drawableRight屬性的圖片設置點擊事件,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • 非常實用的側(cè)滑刪除控件SwipeLayout

    非常實用的側(cè)滑刪除控件SwipeLayout

    這篇文章主要為大家詳細介紹了非常實用的側(cè)滑刪除控件SwipeLayout,類似于QQ側(cè)滑點擊刪除效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-08-08
  • Android實現(xiàn)加載圈

    Android實現(xiàn)加載圈

    這篇文章主要為大家詳細介紹了Android實現(xiàn)加載圈,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • Android自定義View繪制貝塞爾曲線中小紅點的方法

    Android自定義View繪制貝塞爾曲線中小紅點的方法

    貝塞爾曲線的本質(zhì)是通過數(shù)學計算的公式來繪制平滑的曲線,分為一階,二階,三階及多階。但是這里不講數(shù)學公式和驗證,那些偉大的數(shù)學家已經(jīng)證明過了,所以就只講講Android開發(fā)中的運用吧
    2023-02-02
  • Android跳轉(zhuǎn)系統(tǒng)設置Settings的各個界面詳解

    Android跳轉(zhuǎn)系統(tǒng)設置Settings的各個界面詳解

    系統(tǒng)設置Settings中定義的一些常用的各界面ACTION常量,下面這篇文章主要給大家介紹了關于Android跳轉(zhuǎn)系統(tǒng)設置Settings的各個界面,文中介紹非常詳細,需要的朋友可以參考下
    2023-01-01
  • Android MarkTipsView文字標識控件使用方法

    Android MarkTipsView文字標識控件使用方法

    這篇文章主要為大家詳細介紹了Android MarkTipsView文字標識控件的使用方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-04-04

最新評論

桐庐县| 怀远县| 通渭县| 泾阳县| 新河县| 巴彦淖尔市| 宜章县| 柘城县| 三都| 南华县| 博爱县| 乐亭县| 荃湾区| 临沭县| 梓潼县| 平果县| 循化| 正宁县| 泰安市| 岐山县| 墨江| 瓮安县| 永定县| 万安县| 乐至县| 商城县| 南汇区| 阜新| 陇南市| 开江县| 汉沽区| 平江县| 东丽区| 辽源市| 新密市| 湟源县| 卢龙县| 藁城市| 肥乡县| 盱眙县| 惠州市|