1. 程式人生 > >SwipeToLoadLayout佈局中新增自定義控制元件仿美團、餓了嗎等下拉效果

SwipeToLoadLayout佈局中新增自定義控制元件仿美團、餓了嗎等下拉效果

  SwipeToLoadLayout是一個可重用的下拉重新整理和上拉載入控制元件,理論上支援各種View和ViewGroup(ListView,ScrollView,RecyclerView,GridView,WebView,Linearlayout,RelativeLayout,FrameLayout,ImageView,TextView等等)的重新整理和載入,還支援自動重新整理,手動重新整理,自動載入,手動載入,禁止重新整理,禁止載入等操作,同時也可以自定義頭部和尾部,頭部還分classic,above,blow,scale四種類型,還有自動重新整理的效果,體驗也很流暢。
  這裡我們使用它來實現我們所常見的幾種下拉重新整理效果,如百度外賣、餓了嗎、京東商城、美團外賣、天貓、微博及天氣的下拉重新整理效果;
  第一步:首先在build.gradle(Project:專案名稱)中的repositories下面新增JitPack程式碼庫,如下:

    repositories {
        ......
        maven { url "https://jitpack.io" }
    }

  第二步:在build.gradle(Module:app)中新增依賴項,如下:

    dependencies {
        compile 'com.github.Aspsine:SwipeToLoadLayout:1.0.4'
}

  第三步:接下來自定義頭部重新整理控制元件,這裡筆者先自定義了一個餓了嗎的下拉重新整理控制元件,自定義頭部重新整理控制元件需要繼承自SwipeRefreshHeaderLayout,程式碼如下:

public class ELeMaRefreshHeaderView extends SwipeRefreshHeaderLayout {

    private int mHeaderHeight;

    private boolean rotated = false;

    // 建立Handler傳送延遲執行的指令
    private Handler mHandler = new Handler();
    private ImageView imageViewDrumstick;
    private ImageView imageViewHotPot;
    private
ImageView imageViewRice; private ImageView imageViewVegetable; private RelativeLayout imageViewPotCover; public ELeMaRefreshHeaderView(Context context) { this(context, null); } public ELeMaRefreshHeaderView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ELeMaRefreshHeaderView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mHeaderHeight = getResources().getDimensionPixelOffset(R.dimen.refresh_header_height_cook); } @Override protected void onFinishInflate() { super.onFinishInflate(); imageViewDrumstick = (ImageView) findViewById(R.id.image_view_drumstick); imageViewHotPot = (ImageView) findViewById(R.id.image_view_hot_pot); imageViewRice = (ImageView) findViewById(R.id.image_view_rice); imageViewVegetable = (ImageView) findViewById(R.id.image_view_vegetable); imageViewPotCover = (RelativeLayout) findViewById(R.id.image_view_pot_cover); } @Override public void onRefresh() { //正在重新整理,執行動畫 imageViewPotCover.setVisibility(View.INVISIBLE); imageViewDrumstick.setVisibility(View.VISIBLE); imageViewHotPot.setVisibility(View.VISIBLE); imageViewRice.setVisibility(View.VISIBLE); imageViewVegetable.setVisibility(View.VISIBLE); imageViewPotCover.setAlpha(0); startAnimation(); } @Override public void onPrepare() { } @Override public void onMove(int y, boolean isComplete, boolean automatic) { if (!isComplete) { if (y >= mHeaderHeight) { if (!rotated) { rotated = true; } } else if (y < mHeaderHeight) { float tan = (float) (y - mHeaderHeight * 2 / 3) / (float) (imageViewPotCover.getWidth()); int angle = (int) (tan * 90); if (angle > 30) { angle = 30; } if (angle < 0) { angle = 0; } imageViewPotCover.setRotation(-angle); } } } @Override public void onRelease() { Log.d("CookRefreshHeaderView", "onRelease()"); } @Override public void onComplete() { rotated = false; mHandler.removeCallbacksAndMessages(null); imageViewPotCover.setVisibility(View.VISIBLE); imageViewDrumstick.setVisibility(View.GONE); imageViewHotPot.setVisibility(View.GONE); imageViewRice.setVisibility(View.GONE); imageViewVegetable.setVisibility(View.GONE); imageViewPotCover.setAlpha(1); imageViewPotCover.setRotation(0); float[] defaultPoint = {0, 0}; startParabolaAnimation(imageViewDrumstick, defaultPoint, defaultPoint, defaultPoint); startParabolaAnimation(imageViewHotPot, defaultPoint, defaultPoint, defaultPoint); startParabolaAnimation(imageViewRice, defaultPoint, defaultPoint, defaultPoint); startParabolaAnimation(imageViewVegetable, defaultPoint, defaultPoint, defaultPoint); } @Override public void onReset() { rotated = false; } private void startAnimation() { float x = 0; float y = 0; final float[] startPoint = {x, y}; Random random = new Random(); int nextInt = random.nextInt(50); final float[] endPoint = {nextInt + 180, nextInt + 30}; final float[] midPoint = {nextInt + 100, nextInt - 70}; nextInt = random.nextInt(40); final float[] endPoint2 = {nextInt + 160, nextInt + 40}; final float[] midPoint2 = {nextInt + 80, nextInt - 80}; nextInt = random.nextInt(30); final float[] endPoint3 = {nextInt - 200, nextInt + 40}; final float[] midPoint3 = {nextInt - 100, nextInt - 70}; nextInt = random.nextInt(60); final float[] endPoint4 = {nextInt - 170, nextInt + 45}; final float[] midPoint4 = {nextInt - 80, nextInt - 80}; mHandler.postDelayed(new Runnable() { @Override public void run() { startParabolaAnimation(imageViewDrumstick, startPoint, endPoint, midPoint); } }, 100); mHandler.postDelayed(new Runnable() { @Override public void run() { startParabolaAnimation(imageViewHotPot, startPoint, endPoint2, midPoint2); } }, 200); mHandler.postDelayed(new Runnable() { @Override public void run() { startParabolaAnimation(imageViewRice, startPoint, endPoint3, midPoint3); } }, 300); mHandler.postDelayed(new Runnable() { @Override public void run() { startParabolaAnimation(imageViewVegetable, startPoint, endPoint4, midPoint4); } }, 400); mHandler.postDelayed(new Runnable() { @Override public void run() { startAnimation(); } }, 500); } /** * 拋物線動畫 * * @param view * @param startPoint 起點座標 * @param endPoint 結束點座標 * @param midPoint 中間點座標 * @return */ public static ObjectAnimator startParabolaAnimation(final View view, float[] startPoint, float[] endPoint, float[] midPoint) { //分200幀完成動畫 int count = 200; //動畫時間持續600毫秒 int duration = 600; Keyframe[] keyframes = new Keyframe[count]; final float keyStep = 1f / (float) count; float key = keyStep; //計算並儲存每一幀x軸的位置 for (int i = 0; i < count; ++i) { keyframes[i] = Keyframe.ofFloat(key, i * getDx(startPoint, endPoint) / count + startPoint[0]); key += keyStep; } PropertyValuesHolder pvhX = PropertyValuesHolder.ofKeyframe("translationX", keyframes); key = keyStep; //計算並儲存每一幀y軸的位置 for (int i = 0; i < count; ++i) { keyframes[i] = Keyframe.ofFloat(key, getY(startPoint, endPoint, midPoint, i * getDx(startPoint, endPoint) / count + startPoint[0])); key += keyStep; } PropertyValuesHolder pvhY = PropertyValuesHolder.ofKeyframe("translationY", keyframes); ObjectAnimator yxBouncer = ObjectAnimator.ofPropertyValuesHolder(view, pvhY, pvhX).setDuration(duration); //開始動畫 yxBouncer.start(); return yxBouncer; } private static float getDx(float[] startPoint, float[] endPoint) { return endPoint[0] - startPoint[0]; } private static float getDy(float[] startPoint, float[] endPoint) { return endPoint[1] - startPoint[1]; } /** * 這裡是根據三個座標點{startPoint,endPoint,midPoint}計算出來的拋物線方程 * y = ax² + bx + c * * @param x * @return y */ private static float getY(float[] startPoint, float[] endPoint, float[] midPoint, float x) { float x1 = startPoint[0]; float y1 = startPoint[1]; float x2 = endPoint[0]; float y2 = endPoint[1]; float x3 = midPoint[0]; float y3 = midPoint[1]; float a, b, c; a = (y1 * (x2 - x3) + y2 * (x3 - x1) + y3 * (x1 - x2)) / (x1 * x1 * (x2 - x3) + x2 * x2 * (x3 - x1) + x3 * x3 * (x1 - x2)); b = (y1 - y2) / (x1 - x2) - a * (x1 + x2); c = y1 - (x1 * x1) * a - x1 * b; return a * x * x + b * x + c; } }

  第四步:佈局檔案中使用我們自定義控制元件,如下:

<neu.cn.swiperefreshdemo.customview.ELeMaRefreshHeaderView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="90dp">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_horizontal|bottom">

            <ImageView
                android:id="@+id/image_view_drumstick"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginBottom="24dp"
                android:src="@drawable/icon_cook_01" />

            <ImageView
                android:id="@+id/image_view_hot_pot"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginBottom="24dp"
                android:src="@drawable/icon_cook_02" />

            <ImageView
                android:id="@+id/image_view_rice"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginBottom="24dp"
                android:src="@drawable/icon_cook_03" />

            <ImageView
                android:id="@+id/image_view_vegetable"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginBottom="24dp"
                android:src="@drawable/icon_cook_04" />

        </RelativeLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:orientation="vertical">

            <RelativeLayout
                android:id="@+id/image_view_pot_cover"
                android:layout_width="50dp"
                android:layout_height="30dp"
                android:rotation="-0"
                android:transformPivotX="0dp"
                android:transformPivotY="30dp">

                <ImageView
                    android:layout_width="50dp"
                    android:layout_height="18dp"
                    android:layout_alignParentBottom="true"
                    android:layout_centerHorizontal="true"
                    android:scaleType="fitXY"
                    android:src="@drawable/icon_cook_pan_cover" />

            </RelativeLayout>

            <RelativeLayout
                android:layout_width="60dp"
                android:layout_height="30dp">

                <ImageView
                    android:id="@+id/image_view_pot"
                    android:layout_width="60dp"
                    android:layout_height="match_parent"
                    android:layout_centerInParent="true"
                    android:scaleType="fitXY"
                    android:src="@drawable/icon_cook_pan" />

                <ImageView
                    android:layout_width="16dp"
                    android:layout_height="16dp"
                    android:layout_alignParentBottom="true"
                    android:layout_centerHorizontal="true"
                    android:layout_marginBottom="10dp"
                    android:src="@drawable/icon_cook_03" />

                <ImageView
                    android:layout_width="20dp"
                    android:layout_height="20dp"
                    android:layout_alignParentBottom="true"
                    android:layout_marginBottom="4dp"
                    android:layout_marginLeft="10dp"
                    android:src="@drawable/icon_cook_04" />

                <ImageView
                    android:layout_width="16dp"
                    android:layout_height="16dp"
                    android:layout_alignParentBottom="true"
                    android:layout_alignParentRight="true"
                    android:layout_marginBottom="4dp"
                    android:layout_marginRight="10dp"
                    android:src="@drawable/icon_cook_01" />

                <ImageView
                    android:layout_width="46dp"
                    android:layout_height="28dp"
                    android:layout_centerInParent="true"
                    android:scaleType="fitXY"
                    android:src="@drawable/icon_cook_water" />

            </RelativeLayout>

        </LinearLayout>

    </RelativeLayout>

</neu.cn.swiperefreshdemo.customview.ELeMaRefreshHeaderView>

  這裡筆者為了減少佈局的巢狀,使用了引入佈局,在fragment_elema.xml中引入上面的佈局檔案,如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorPrimary">

    <com.aspsine.swipetoloadlayout.SwipeToLoadLayout
        android:id="@+id/swipe_to_loadlayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <include
            android:id="@id/swipe_refresh_header"
            layout="@layout/layout_elema_header" />

        <TextView
            android:id="@id/swipe_target"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#1E90FF"
            android:gravity="center"
            android:text="下拉重新整理"
            android:textSize="24sp" />

    </com.aspsine.swipetoloadlayout.SwipeToLoadLayout>

</RelativeLayout>

注意: SwipeToLoadLayout中佈局包裹的view的id是指定的,筆者也不懂為何要指定,不要亂改就好啦!否者會找不到的;

<item name="swipe_target" type="id" />  重新整理目標
<item name="swipe_refresh_header" type="id" />  重新整理頭部
<item name="swipe_load_more_footer" type="id" />  重新整理尾部

  第五步:程式碼中載入fragment佈局檔案,併為SwipeToLoadLayout設定下拉重新整理監聽事件;

public class ELeMaRefreshFragment extends Fragment implements OnRefreshListener {

    private SwipeToLoadLayout mSwipeToLoadLayout;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View mFragmentView = inflater.inflate(R.layout.fragment_elema, container, false);
        mSwipeToLoadLayout = (SwipeToLoadLayout) mFragmentView.findViewById(R.id.swipe_to_loadlayout);
        mSwipeToLoadLayout.setOnRefreshListener(this);
        mSwipeToLoadLayout.postDelayed(new Runnable() {
            @Override
            public void run() {
                mSwipeToLoadLayout.setRefreshing(true);
            }
        }, 100);
        return mFragmentView;
    }

    @Override
    public void onRefresh() {
        mSwipeToLoadLayout.postDelayed(new Runnable() {
            @Override
            public void run() {
                mSwipeToLoadLayout.setRefreshing(false);
            }
        }, 3000);
    }
}

  最後,在MainActivity中動態載入碎片,如下:

ELeMaRefreshFragment eLeMaRefreshFragment = new ELeMaRefreshFragment();
mFragmentManager.beginTransaction().replace(R.id.content_frame, eLeMaRefreshFragment).commit();

餓了嗎下拉重新整理效果如下所示:
這裡寫圖片描述
筆者還寫了其它六個下拉重新整理效果,思路大致相同,這裡簡單展示幾個,大家可以去下載原始碼,然後自己執行看一下效果,便於理解程式碼;
京東下拉重新整理效果如下所示:
這裡寫圖片描述
美團外賣下拉重新整理效果如下所示:
這裡寫圖片描述
百度外賣下拉重新整理效果如下所示:
這裡寫圖片描述
天貓下拉重新整理效果如下所示:
這裡寫圖片描述
完整程式碼:
更多效果展示及筆者demo的原始碼,可以去筆者的GitHub中檢視,歡迎大家下載;
原始碼地址:https://github.com/henryneu/SwipeRefreshDemo

相關推薦

SwipeToLoadLayout佈局新增定義控制元件仿效果

  SwipeToLoadLayout是一個可重用的下拉重新整理和上拉載入控制元件,理論上支援各種View和ViewGroup(ListView,ScrollView,RecyclerView,GridView,WebView,Linearlayout,Rela

Android定義控制元件——仿淘寶網易彩票廣告條Banner的製作

最近翻看以前的某專案時,發現了一個極其常用的效果——廣告條,或者也稱不上自定義元件,但是使用頻率還是相當普遍的。 開啟市面上各大App主介面,或多或少會出現這樣的東西,甚至一個應用中出現N多個,這種展示廣告的效果,不僅動態效果好,而且眾所周知的“不佔屏”,想想在手機裝

Android引入佈局和和定義控制元件

首先是引入佈局: 1.我們自己新建一個layout,就是一個標題欄。 2.然後在我們的mainactivity_layout中使用一個語句就可以實現。 <?xml version="1.0" encoding="utf-8"?> <LinearLayout

一起Talk Android吧(第一百回:Android使用定義控制元件

各位看官們,大家好,上一回中咱們說的是Android中使用自定義佈局的例子,這一回說的例子是Android中使用自定義控制元件。閒話休提,言歸正轉。讓我們一起Talk Android吧! 看官們,我們在上一回中通過自定義佈局巧妙地實現了分隔線,不過這個分隔線中看

C#定義控制元件的屬性事件及一些相關特性的總結

今天學習了下C#使用者控制元件開發新增自定義屬性的事件,主要參考了MSDN,總結並實驗了一些用於開發自定義屬性和事件的特性(Attribute)。 在這裡先說一下我的環境: 作業系統:Windows7旗艦版(Service Pack 1) VS版本:Microsoft

Qt QScrollArea顯示滾動條(新增定義控制元件)

最近在做專案,想要使用一個帶滾動條的窗體來顯示一些資訊,可以自己重寫一個區域再關聯一個QScrollBar,但是這樣一來,工作量貌似挺大,之前就知道有QScrollArea物件,但是一直沒用過,心裡想著應該可以在上面布一些控制元件物件,但是後來查了幫助文件,怎麼也沒發現類似

android佈局複用 & 定義控制元件

        android中View的概念非常重要,它是使用者和手機互動的橋樑,就是說android手機向用戶展示的介面內容、你向手機輸入的內容的這個互動過程是通過View來實現的。android的佈局是在View的基礎上建立起來的,這個概念簡單理解就是多個控制元件組成

WPF呼叫類庫定義控制元件

今天在專案中遇到了需要呼叫類庫中的自定義控制元件,控制元件繼承的是ContentControl ,但是發現無論如何都呼叫不成功,除錯了N個小時後發現,缺少檔案,故,不能上來直接建立類庫,需要先建立WPF程式,然後在建立自定義控制元件,最後刪除App.xaml等,總的來說就是,

winform一個定義控制元件呼叫父窗體的事件

//1定義個委託用來掉方法 MyEventArgs類要你自己寫裡面可以加些你認為有用的屬性,不想用的話可以不加這個引數, public delegate void 相關事件的委託(object sen

ActionBar 新增定義控制元件居中

在ActionBar中新增控制元件,使其內容在ActionBar Title與Menu之間居中 getActionBar().setDisplayOptions( Ac

asp.net使用者定義控制元件呼叫另外一個使用者定義控制元件的方法

昨天同事問我一個問題,說是在一個頁面中有二個使用者自定義控制元件,他現在想在其中一個使用者自定義控制元件中呼叫另外一個使用者自定義控制元件的一個方法。當時感覺很奇怪,為什麼要這樣呢。可是他說他要完成這樣一個功能,所以我就簡單的用一個反射的功能來完成它。試了一下,還行功能可以實

在對話方塊程式使用定義控制元件-XT介面庫

1.      新建一個MFC對話方塊工程,以報表控制元件為例。 在stdarx.h標頭檔案中加入XT介面庫的包含檔案: #include "XTToolKitPro.h"// xt介面庫 2.      向對話方塊中拖一個自定義控制元件(Custom Control),修

定義重新整理控制元件-仿重新整理效果

概述   下拉重新整理是平時專案中最常用的功能,今天要說的就是如何自定義下拉重新整理控制元件。   第三方重新整理控制元件也比較多,例如Android-PullToRefresh,XListView等,但是這些控制元件自定義重新整理頭部不那麼容易擴充套件,它

Android定義控制元件——仿微信半透明載入框

在我們的app中不免回遇到耗時操作,這時我們給個載入框進行過度,使使用者體驗大大加強。 一、CustomDialog類的編寫 package com.daqsoft.jingguan.weight; import android.app.Dialo

Android定義控制元件-仿淘寶ios客戶端天貓商品詳情介面動效

效果圖 原始碼和例子 效果描述 一個自定義控制元件繼承自ScrollView,下拉時header會放大鬆開後會恢復原狀,上滑時header會被下面的內容吃掉蓋住而且會稍稍往上滑,在header高度範圍內滑動時導航欄背景和導航欄的按鈕會反向改變透明度形成一種對比

【Android】定義控制元件-仿QQ聯絡人側滑條目,右側滑選單。

一直沒有寫部落格的習慣,一直都是看別人的部落格,學習別人的東西。平時工作中總會遇到或大或小的問題,往往是上百度CSDN查詢答案。今天嘗試著寫部落格,一是更加深入地熟悉一下部落格;二是轉變一下學習方式;三是把自己所學的東西分享出來,幫助別人的同時也提升了自己!

【Android】定義控制元件——仿天貓Indicator

今天來說說類似天貓的Banner中的小圓點是怎麼做的(圖中綠圈部分) 在學習自定義控制元件之前,我用的是非常二的方法,直接在佈局中放入多個ImageView,然後程式碼中根據Pager切換來改變圖片。這種方法雖然可以在切換完成後正確顯示小圓點,但是卻做不到如下圖中的切換

定義控制元件 仿微信朋友圈文字展開全文功能

自定義TextView仿微信朋友圈文字資訊,展開全文功能 程式碼及註釋如下: 首先寫一個xml檔案 showmore.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout x

Android定義控制元件仿儀表盤進度控制元件ArcProgressBar

開門見山,效果圖如下: 這種效果經常會遇到,但卻一直不知道這個效果圖應該怎麼描述,所以暫且以“儀表盤進度控制元件”來描述,各位博友如果有更好的描述這種效果的詞彙,請回復博文告訴我,在此先謝謝各位博友了! 其實做出這樣的效果並不困難,只需要瞭解自定義控制元

定義控制元件 | 仿《最有物》點贊效果

前言 最近在跟著Hencode學習自定義控制元件,一直想著自己能夠照著別人寫的demo寫一個好看的View,就科學上網看看了別人的一些demo,看到了一位大神寫的模仿《最美有物》的點贊效果,覺得勝似喜歡,於是也跟著寫了一個demo。主要是看著流程分析自己擼出來