一行代碼實現ViewPager卡片效果

前言

最近看到愈來愈多ViewPager卡片效果,甚至本身公司的產品也用到了。正如本身看到這個效果時,心裏的想法是,這個簡單,github一搜一籮筐,看了不下4個庫,使用起來都比較麻煩,不是說寫得很差,都是這方面的先驅者,值得學習。關鍵是在這三天一小需求,一週一大需求的年代裏,不一行代碼搞定,怎麼完成任務啊,更況且這體現不了咱們優秀程序員的逼格啊!開個玩笑,哈哈!我封裝了常見的卡片效果,達到一行代碼就能使用。No picture,say a J8!全程採用圖文並茂的形式分析,幫助你理解。git

使用方法

viewPager.bind(getSupportFragmentManager(), new MyCardHandler(), Arrays.asList(imageArray));複製代碼

其中ViewPager是咱們自定義的,名曰CardViewPager。可能比較糾結的就是MyCardHandler。程序員

public class MyCardHandler implements CardHandler
  
  
  

 
  
  { @Override public View onBind(final Context context, final String data, final int position) { View view = View.inflate(context, R.layout.item, null); ImageView imageView = (ImageView) view.findViewById(R.id.image); Glide.with(context).load(data).into(imageView); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(context, "data:" + data + "position:" + position, Toast.LENGTH_SHORT).show(); } }); return view; } } public interface CardHandler 
 
  
    { View onBind(Context context, T data, int position); } 
   

 複製代碼

你能夠在onBind中操做你的View,這裏提供數據以及該view的索引。就跟ListView的getView相似。github

差很少了,使用方法就是這麼簡單。可是臺上十分鐘,臺下十年功。app

效果

原理解析

從視覺效果來看,最早映入眼簾的是卡片,其次是卡片左右微微探頭,暗中觀察的兩張卡片。卡片效果想都不用想,CradView。關於這個控件的封裝,我放在了最後,由於咱們對於view是徹底開放的,只要是這樣的滑動效果,能夠填充任何的view,不侷限於CardView,對於view更多佈局效果,我也會推薦幾種。ide

咱們先來看看暗中觀察的這兩個傢伙究竟是怎麼實現的。佈局

這是咱們最多見的ViewPager效果。學習

PageTransformer

咱們再來看看這樣的效果。動畫

不知道小夥伴們看清楚了嗎?我看清楚了,前一個ViewPager在縮小和向右偏移,後一個ViewPager在放大和向左偏移。WTF?縮放我看到了,前面一個不是在左移嗎?你跟我說右移?ui

同窗,請先放下你手中的菜刀,聽我細細道來,咱們先看看源代碼。spa

@Override
public void transformPage(View page, float position) {
    if (mViewPager == null) {
        mViewPager = (ViewPager) page.getParent();
    }

    int leftInScreen = page.getLeft() - mViewPager.getScrollX();
    int centerXInViewPager = leftInScreen + page.getMeasuredWidth() / 2;
    int offsetX = centerXInViewPager - mViewPager.getMeasuredWidth() / 2;
    float offsetRate = (float) offsetX * 0.38f / mViewPager.getMeasuredWidth();
    float scaleFactor = 1 - Math.abs(offsetRate);
    if (scaleFactor > 0) {
        page.setScaleX(scaleFactor);
        page.setScaleY(scaleFactor);
        page.setTranslationX(-mMaxTranslateOffsetX * offsetRate);
    }
}複製代碼

這是PageTransformer核心方法,直接說你可能比較懵逼,秉承圖文並茂的style,先來看看兩張圖,圖也是盜的,有現成的爲何還要本身去作,就算你如今不這麼認爲,過幾年就這麼想了,我賭半包辣條。

這是你定製ViewPager各類炫酷的切換效果的基礎,view就是ViewPager填充的view或者是fragment中的填充view。position上面已經很清楚,同窗們大可本身玩玩,只有本身玩過,才知道什麼是浪費時間,沒毛病。

如今,咱們來分析下該庫爲何要這麼寫?一行一行分析,才能虎得住人,先來分析前3行代碼,拿到ViewPager的引用,判空是防止重複獲取,爲何page.getParent獲取的是ViewPager,你就把它當成橫鋪的LinearLayout你就理解了(但內部並非這樣的,由於它有銷燬和建立這麼一說)。

接下來咱們來看看leftInScreen,page.getLeft()表示當前view相對於父容器(ViewPager)離左邊的距離,mViewPager.getScrollX()表示ViewPager在X軸滑動的距離(內容),那麼很明顯啦,leftInScreen就是該view的左邊距離當前view(ViewPager的當前Item)的左邊的距離(由於該view的padding是算在getLeft裏面的,所以得減去,padding值有正負,左正右負)。

這樣說來centerXInViewPager就很好理解啦,由leftInScreen加上該view的寬度的一半,那就是從該view的中間開始算起。

offsetX相對來講比較繞,但也是核心所在。它是由centerXInViewPager加上ViewPager寬度的一半。通常來講,offsetX與leftInScreen是相等的,先加上一半再減去一半有毛區別,但這一半不必定相等,例如ViewPager加上padding。若是你對上面的解析都理解了的話,那就很簡單了,就是該view相對於當前item的偏移量。

空間想象力強的人已經理解了,但我確定會照顧全部的同窗,下面給出圖,同窗們能夠參考着圖來理解上面的概念。

offsetRate和scaleFactor比較好理解啦,前者是offsetX相對於ViewPager的一個比值,後者用了Math.abs取絕對值是由於上面所返回的值都是有正負之分的,左負右正。

最後的屬性修改我就不想多說了,做爲一名有信仰的Android程序員,這個都不知道,我仍是會選擇原諒你。這裏比較卡人的就是爲何要setTranslationX,就是由於咱們調用了setScaleX,你想一想你縮放了,是否是中間空出了這麼點距離?因此這個就是彌補中間空出的,讓人看上去一直是銜接的,你徹底能夠改,咱們這裏最大值取180dp,這也是爲何我一開始說前一個在右移的緣由。

setPageTransformer(false, new CardTransformer(context));複製代碼

這樣就設置進去了,這裏我解釋下,第一個參數,咱們庫其實可有可無,但做爲一名有逼格的程序員,不刨根問底,實在對不起本身。

* @param reverseDrawingOrder true if the supplied PageTransformer requires page views
*                            to be drawn from last to first instead of first to last.複製代碼

大體意思是說,若是爲false,那麼就從第一頁開始繪製至最後一頁,爲true就是從最後一頁開始繪製至第一頁,若是對佈局FrameLayout比較熟悉的同窗,我相信難不倒你。

PageTransformer可讓你設計出超炫酷的切換動畫,什麼立體翻轉啊,什麼淡入淡出啊,等等。

你們也能夠看看洋哥的《Android 實現個性的ViewPager切換動畫 實戰PageTransformer(兼容Android3.0如下)》

ViewPager

咱們的主角但是暗中觀察的兩個傢伙啊,主角確定是要登場啊。如今咱們但願ViewPgaer可以留出兩塊區域來顯示它們。回想一下,一開始,我讓大家把ViewPager當成LinearLayout橫鋪,這裏我依然這麼作,ViewPager顯示視圖默認是隻顯示當前Item的,但咱們只要把內容擠一擠不就有了,擠內容常見的就是padding了,若是仔細看上面部分的同窗應該早就猜出來了。這裏我設置了40dp的padding,來看看效果。

看了效果圖的同窗又要罵娘了,右邊有一小塊覆蓋了,最重要根本沒看到那兩傢伙啊,前者是由於咱們前面說的第一個參數爲false,右邊覆蓋左邊,這也證明了咱們的結論。後者是由於咱們沒加clipToPadding屬性設爲false。看看名字就是知道了,是否剪裁padding,我不剪裁不就顯示出來了?咱們再來看看效果。

有點意思了哈,要是視圖之間再空開點就完美了,剛纔咱們用到了padding,不妨用用margin,俗話說外事不決問margin,內事不決問padding,很熟悉,但好像哪裏出了點問題,沒毛病,就是這樣的,圈起來,這是重點。

int margin = typedArray
                .getDimensionPixelOffset(R.styleable.CardViewPager_card_margin,
                        (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, displayMetrics));
setPageMargin(margin);複製代碼

看看最終的效果。

是否是很完美,這裏關於CardView的使用我就不介紹了,我也提供了一個可自定義寬高比例,默認支持陰影的CardView,稍微要注意的一點是,若是用本身的CardView,最好加上

app:cardPreventCornerOverlap="true"
app:cardUseCompatPadding="true"複製代碼

5.0以後默認是false,也就是說不支持陰影(陰影被遮住了)。

Adapter

如今來分析如何構建視圖的,這裏咱們用到的fragment,由於谷歌已經爲咱們處理了不少加載問題,拿來用就是,這裏咱們adapter繼承於FragmentStatePagerAdapter,是由於咱們卡片可能不少。

來看一看核心代碼:

Context context = getContext();
List
  
  
  

 
  
  cardItems = new ArrayList 
 
  
    (); for (int i = 0, size = data.size(); i < size; i++) { T t = data.get(i); CardItem 
   
     item = new CardItem 
    
      (); item.bindHandler(handler); item.bindData(t, i); cardItems.add(item); } if (mHandler == null) { throw new RuntimeException("please bind the handler !"); } return mHandler.onBind(mContext, mData, mPosition); 
     
    
   

 複製代碼

這裏咱們採用mHandler去從新構造View,而不是複用構造出來的View,是配合FragmentStatePagerAdapter使用的,可以更好利用資源和釋放資源,否則很容易形成OOM哦。

結束語

文章差很少就到這裏,我相信你如今有不少疑問,有不少想法。這些疑問並非對我上面闡述的概念,而是對ViewPager的理解,或許你已經能夠用另外一種方法實現了,或許你已經在敲之前想設計可是沒實現的代碼了。若是對你有一點點幫助,那麼個人目的就達到了。

傳送門

GitHub:github.com/crazysunj/C…

博客:一行代碼實現ViewPager卡片效果

相關文章
相關標籤/搜索