具有回彈效果的RecyclerView,RecyclerView外層可滾動容器
一個具有回彈效果的RecyclerView,本文通過實現RecyclerView外層的容器的上下滑動達到了回彈的效果,在整個滑動的事件分發機制中,外層容器的事件攔截機制進行判斷是否攔截事件,判斷標準為RecyclerView是否滾動到了第一個item或者最後一個item,如果下滑滾動到了第一個item還繼續下滑,外層容器的事件攔截機制將此事件進行攔截,交給外層容器的onTouchEvent進行消費;上滑,同理。如果不攔截,將交給子view即RecyclerView進行消費。
下面是我自己總結的事件分發機制,希望對你能有幫助。不清楚的童鞋可以打印出來進行記憶。每天翻看幾遍,死記住,以後你會慢慢的明白其原理。
dispatchTouchEvent(MotionEvent ev)事件分發
1.返回true,表示這件事由dispatchTouchEvent消費掉了,事件停止向下傳遞。
2.返回false,表示事件不分發,返回給上一層activity或者父控制元件中的onTouchEvent進行消費。
3.返回super.dispatchTouchEvent(ev),事件交由當前view的onlnterceptTouchEvent進行事件攔截。
onlnterceptTouchEvent(MotionEvent ev) 事件攔截
1.返回true,表示事件攔截成功,交由當前view的onTouchEvent消費。
2.返回false,表示事件放行,將事件傳遞給下一個子view進行處理(下一個子view的dispatchTouchEvent進行處理)。
3.返回super.onlnterceptTouchEvent(ev),表示事件攔截,交由當前view的onTouchEvent消費,同true。
onTouchEvent(MotionEvent ev) 事件響應
前提:當前控制元件事件獲得響應
1.返回 false,事件將從當前的view向上傳遞,由父view的onTouchEvent來接收消費事件。
2.返回 true,事件被當前的view接收並消費掉。
3.返回onTouchEvent(ev),同false。
下面是這個容器的程式碼:
容器類
import android.content.Context; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; import android.widget.Scroller; /** * Created by Sick on 2016/8/8. * 自定義可滾動元件的彈性容器,仿IOS回彈效果 */ public class RVScrollLayout extends LinearLayout { private final String TAG = this.getClass().getSimpleName(); /** * 容器中的元件 */ private View convertView; /** * 如果容器中的元件為RecyclerView */ private RecyclerView recyclerView; /** * 滾動結束 */ private int mStart; /** * 滾動結束 */ private int mEnd; /** * 上一次滑動的座標 */ private int mLastY; /** * 滾動輔助類 */ private Scroller mScroller; public RVScrollLayout(Context context) { this(context, null); } public RVScrollLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RVScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mScroller = new Scroller(context); } @Override protected void onFinishInflate() { super.onFinishInflate(); if (getChildCount() > 1) { throw new RuntimeException(RVScrollLayout.class.getSimpleName() + "只能有一個子控制元件"); } convertView = getChildAt(0); //TODO 可以拓展ListView等可滑動的元件 if (convertView instanceof RecyclerView) { recyclerView = (RecyclerView) convertView; } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (changed) { View view = getChildAt(0); view.layout(left, top, right, bottom); } } @Override public boolean onTouchEvent(MotionEvent event) { int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mStart = getScrollY(); break; case MotionEvent.ACTION_MOVE: if (!mScroller.isFinished()) { mScroller.abortAnimation(); //終止動畫 } scrollTo(0, (int) ((mLastY - y) * 0.4)); break; case MotionEvent.ACTION_UP: mEnd = getScrollY(); int dScrollY = mEnd - mStart; /** * 回彈動畫,第一二個引數為開始的x,y * 第三個和第四個引數為滾動的距離(注意方向問題) * 第五個引數是回彈時間 */ mScroller.startScroll(0, mEnd, 0, -dScrollY, 1000); break; } postInvalidate(); return true; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int y = (int) ev.getY(); Log.d(TAG, "相對於元件滑過的距離==getY():" + y); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mLastY = y; break; case MotionEvent.ACTION_MOVE: /** * 下面兩個判斷來自於 BGARefreshLayout 框架中的判斷,github 上搜索 BGARefreshLayout */ if (convertView instanceof RecyclerView) { if (y - mLastY > 0) { if (Util.isRecyclerViewToTop(recyclerView)) { Log.d(TAG, "滑倒頂部時時間攔截成功"); return true; } } if (y - mLastY < 0) { if (Util.isRecyclerViewToBottom(recyclerView)) { Log.d(TAG, "滑倒底部時時間攔截成功"); return true; } } } break; } return false; } @Override public void computeScroll() { super.computeScroll(); if (mScroller.computeScrollOffset()) { scrollTo(0, mScroller.getCurrY()); postInvalidate(); } } }
區別scrollBy()和scrollTo(),前者是在上一個動作的基礎上移動,後者是從view的最開始移動,移動是相對於括號中的引數而言。
還有最重要的兩點:第一,它們移動的是當前view中的content,當前viewgroup中的子view,在recyclerview中相當於移動的它裡面的子item;第二,他們的移動,為什麼說他們的移動是一個重點呢,因為如果你將移動的x,y直接寫入引數,你會發現檢視是反方向的移動,這裡你可以將子item看成一個很大的畫板,手機(recyclerview)看成一箇中空的擋板,移動中空擋板就相當於子item移動了,所以它們的引數應該是一個負值。 即scrollBy(-offsetX,-offsetY)
getScrollY() 獲取的是view或這viewgroup滾動過的距離。
再捋一捋這一流程的事件分發,首先滑動的時候事件是從外層向內層傳遞,本文中外層是容器,內層是recyclerview元件,事件分發給容器,容器進行判斷是否攔截,return true表示攔截,在容器自身的onTouchEvent()中消費,如果return false將交給recyclerview進行消費,這裡將不關注recyclerview。
起初我的思路是重寫recyclerview的事件分發的方法,對外層容器傳遞過來的事件進行不分發返回給上層容器onTouchEvent()進行消費 或者 在它的OnTouchEvent()事件響應中返回return false 交給上層容器onTouchEvent()進行消費來達到容器可以回彈的效果。
父容器的回彈效果可以通過很多種方式進行實現,可以設定父容器的marginTop和marginBottom達到彈性的效果,而本文采用了Scroller輔助類滾動來實現的。
配置類(判斷recyclerview滾動)
import android.graphics.Rect;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;
import android.view.ViewParent;
import java.lang.reflect.Field;
import cn.bingoogolapple.refreshlayout.BGAStickyNavLayout;
/**
* Created by Sick on 2016/8/10.
*/
public class Util {
public static boolean isRecyclerViewToTop(RecyclerView recyclerView) {
if (recyclerView != null) {
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager == null) {
return true;
}
if (manager.getItemCount() == 0) {
return true;
}
if (manager instanceof LinearLayoutManager) {
LinearLayoutManager layoutManager = (LinearLayoutManager) manager;
int firstChildTop = 0;
if (recyclerView.getChildCount() > 0) {
// 處理item高度超過一螢幕時的情況
View firstVisibleChild = recyclerView.getChildAt(0);
if (firstVisibleChild != null && firstVisibleChild.getMeasuredHeight() >= recyclerView.getMeasuredHeight()) {
if (android.os.Build.VERSION.SDK_INT < 14) {
return !(ViewCompat.canScrollVertically(recyclerView, -1) || recyclerView.getScrollY() > 0);
} else {
return !ViewCompat.canScrollVertically(recyclerView, -1);
}
}
// 如果RecyclerView的子控制元件數量不為0,獲取第一個子控制元件的top
// 解決item的topMargin不為0時不能觸發下拉重新整理
View firstChild = recyclerView.getChildAt(0);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) firstChild.getLayoutParams();
firstChildTop = firstChild.getTop() - layoutParams.topMargin - getRecyclerViewItemTopInset(layoutParams) - recyclerView.getPaddingTop();
}
if (layoutManager.findFirstCompletelyVisibleItemPosition() < 1 && firstChildTop == 0) {
return true;
}
}
}
return false;
}
/**
* 通過反射獲取RecyclerView的item的topInset
*
* @param layoutParams
* @return
*/
private static int getRecyclerViewItemTopInset(RecyclerView.LayoutParams layoutParams) {
try {
Field field = RecyclerView.LayoutParams.class.getDeclaredField("mDecorInsets");
field.setAccessible(true);
// 開發者自定義的滾動監聽器
Rect decorInsets = (Rect) field.get(layoutParams);
return decorInsets.top;
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
public static boolean isRecyclerViewToBottom(RecyclerView recyclerView) {
if (recyclerView != null) {
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager == null || manager.getItemCount() == 0) {
return false;
}
if (manager instanceof LinearLayoutManager) {
// 處理item高度超過一螢幕時的情況
View lastVisibleChild = recyclerView.getChildAt(recyclerView.getChildCount() - 1);
if (lastVisibleChild != null && lastVisibleChild.getMeasuredHeight() >= recyclerView.getMeasuredHeight()) {
if (android.os.Build.VERSION.SDK_INT < 14) {
return !(ViewCompat.canScrollVertically(recyclerView, 1) || recyclerView.getScrollY() < 0);
} else {
return !ViewCompat.canScrollVertically(recyclerView, 1);
}
}
LinearLayoutManager layoutManager = (LinearLayoutManager) manager;
if (layoutManager.findLastCompletelyVisibleItemPosition() == layoutManager.getItemCount() - 1) {
BGAStickyNavLayout stickyNavLayout = getStickyNavLayout(recyclerView);
if (stickyNavLayout != null) {
// 處理BGAStickyNavLayout中findLastCompletelyVisibleItemPosition失效問題
View lastCompletelyVisibleChild = layoutManager.getChildAt(layoutManager.findLastCompletelyVisibleItemPosition());
if (lastCompletelyVisibleChild == null) {
return true;
} else {
// 0表示x,1表示y
int[] location = new int[2];
lastCompletelyVisibleChild.getLocationOnScreen(location);
int lastChildBottomOnScreen = location[1] + lastCompletelyVisibleChild.getMeasuredHeight();
stickyNavLayout.getLocationOnScreen(location);
int stickyNavLayoutBottomOnScreen = location[1] + stickyNavLayout.getMeasuredHeight();
return lastChildBottomOnScreen <= stickyNavLayoutBottomOnScreen;
}
} else {
return true;
}
}
} else if (manager instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) manager;
int[] out = layoutManager.findLastCompletelyVisibleItemPositions(null);
int lastPosition = layoutManager.getItemCount() - 1;
for (int position : out) {
if (position == lastPosition) {
return true;
}
}
}
}
return false;
}
public static BGAStickyNavLayout getStickyNavLayout(View view) {
ViewParent viewParent = view.getParent();
while (viewParent != null) {
if (viewParent instanceof BGAStickyNavLayout) {
return (BGAStickyNavLayout) viewParent;
}
viewParent = viewParent.getParent();
}
return null;
}
}
Activity類
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
/**
* Created by Sick on 2016/8/8.
*/
public class TestActivity extends Activity {
private RecyclerView rvCustomList;
private ArrayList<String> data;
private RVAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bbrecyclerveiw);
initData();
initView();
}
private void initData() {
data = new ArrayList<String>();
for (int i = 0; i <20; i++) {
data.add("測試"+i);
}
}
private void initView() {
rvCustomList = (RecyclerView) findViewById(R.id.rv_custom_list);
rvCustomList.setLayoutManager(new LinearLayoutManager(this));
adapter = new RVAdapter();
rvCustomList.setAdapter(adapter);
}
public class RVAdapter extends RecyclerView.Adapter{
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
MyHolderView holder = new MyHolderView(LayoutInflater.from(TestActivity.this).inflate(R.layout.item_data,parent,false));
return holder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof MyHolderView){
((MyHolderView) holder).tvData.setText(data.get(position));
}
}
@Override
public int getItemCount() {
return data.size();
}
private class MyHolderView extends RecyclerView.ViewHolder{
TextView tvData;
public MyHolderView(View itemView) {
super(itemView);
tvData = (TextView) itemView.findViewById(R.id.tv_data);
}
}
}
}
xml檔案
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#00ff00">
<com.song.view.RVScrollLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff0000"
>
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_custom_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0000ff">
</android.support.v7.widget.RecyclerView>
</com.song.view.RVScrollLayout>
</LinearLayout>
程式碼直接copy到你的專案裡面就可以使用,大神勿噴,小弟只是一個beginner,你有更好的想法更健壯的程式碼可以聯絡我![email protected]相關推薦
具有回彈效果的RecyclerView,RecyclerView外層可滾動容器
一個具有回彈效果的RecyclerView,本文通過實現RecyclerView外層的容器的上下滑動達到了回彈的效果,在整個滑動的事件分發機制中,外層容器的事件攔截機制進行判斷是否攔截事件,判斷標準為RecyclerView是否滾動到了第一個item或者最後一個i
angual+mui 雙欄上拉加載,微信裏面禁用默認事件可用,可以防止瀏覽器回彈效果
apply length data mui this reat mobile ng- a10 //html 部分 p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Consolas; color: #2eafa9
原來操控介面可以這麼簡單----安卓上下滑動縮放頂部圖片,左右滑動結束當前Activity,及View柔和回彈效果
先上效果圖: 上傳圖片不能超過2M,費了好大勁。每一張gif動的有點快,將就看。 首先說原理: 為activity的xml檔案根佈局新增setOnTouchListener。上下滑動和左右滑動的所有操作都是在OnTouchListener的onTouch方法中實現的,通過
下拉ScrollView伸縮頭佈局,實現ScrollView回彈效果
專案中用到了商品詳情展示效果,所以立馬想到借鑑天貓商品詳情介面,看了天貓的詳情頁面想到了兩套解決方案。1,使用LitView 新增header監聽listView 的滑動然後根據listView 的滑動距離計算 header應該滑動的距離 和改變header的
Android 自定義ScrollView 支援慣性滑動,慣性回彈效果。支援上拉載入更多
先講下原理: ScrollView的子View 主要分為3部分:head頭部,滾動內容,fooder底部 我們實現慣性滑動,以及回彈,都是靠超過head或者fooder 就重新滾動到 ,內容的頂部或者底部。 之前看了Pulltorefresh 他是通過不斷改變 head或
-webkit-overflow-scrolling 與滾動回彈效果.
插件 列表 卡住 快的 優雅 css 移動設備 分享 兼容性 參考來源:https://developer.mozilla.org/zh-CN/docs/Web/CSS/-webkit-overflow-scrolling https://www.w3cways.
自定義ScrollView 實現上拉下拉的回彈效果--並且子控件中有Viewpager的情況
是否 AS abs pri tar utils lda animation ted onInterceptTouchEvent就是對子控件中Viewpager的處理:左右滑動應該讓viewpager消費 1 public class MyScrollView ext
解決蘋果微信瀏覽器下拉回彈效果
.content是需要滑動的部分 var overscroll = function(el) { el.addEventListener('touchstart', function()
移動端阻止瀏覽器中預設元素滑動回彈效果(橡皮筋效果)
在js檔案中加如下程式碼: document.addEventListener('touchstart',function(e){ e.preventDefault(); //
JavaScript禁止微信瀏覽器下拉回彈效果
本文例項為大家分享了JavaScript禁止微信瀏覽器下拉回彈的效果 方法1: <script type="text/javascript"> var overscroll = function(el){
ScrollView巢狀RecyclerView,RecyclerView總是把它上面的控制元件頂出頁面(頁面出現自己滾動)
ScrollView巢狀RecyclerView,當我離開當前頁面,然後又回來時,RecyclerView就會把它上邊的控制元件都擠出頁面,它顯示在頁面最上邊。 原因應該是RecyclerView搶了焦點,只需要把ScrollView中最上邊的那個控制元件加上幾句程式碼
android ListView 仿IOS 回彈效果
最近看IOS的下拉效果感覺很不錯,當拉倒最上面和最下面的時候繼續拉動會有緩衝,想在android裡面也做一個,到網上到處找,沒有找到好的方法,據說android新的API對ListView有這樣的支援,感覺不是特別好用。 自己利用scroller實現了一下,廢話不多說了直接
實現ViewPager的回彈效果
為了能夠在ViewPager的第一頁和最後一頁左右滑動時候不顯得那麼生硬,通過重寫ViewPager類實現回彈效果。 程式碼很簡單,主要重寫onTouchEvent方法。 程式碼如下: public class BounceBackViewPager
CoordinatorLayout初體驗以及標題欄下方圖片的回彈效果
最近在研究material design ,瞭解到 CoordinatorLayout 這個佈局,所以研究和學習下,寫了個demo.加上拓展仿照了一個標題欄下方圖片的回彈效果,但不是使用CoordinatorLayout 實現的,下面看圖: 第一個效果是使
Android 帶阻尼回彈效果的ScorllView
import android.content.Context; import android.graphics.Color; import android.graphics.Rect; import android.util.AttributeSet; impo
仿IOS回彈效果支援任何控制元件
效果圖: 匯入依賴: dependencies { // ... compile 'me.everything:overscroll-decor-android:1.0.4
Android HorizontalScrollView回彈效果
轉載記錄備份查閱 import android.annotation.SuppressLint; import android.os.Build; import android.util.Log; import android.view.MotionEvent
ViewPager回彈效果
其實在我們很多應用中都看到當ViewPager滑到第一頁或者最後一頁的時候,如果再滑動的時候,就會有一個緩衝的過程,也就是回彈效果。之前在研究回彈效果的時候,也順便實現了ViewPager的回彈效果,其實也很簡單,一下是實現程式碼,註釋比較少: package com.fr
ScrollView實現阻尼回彈效果!
今天跟大夥簡紹個ScrollView的阻尼回彈!下拉到一定程度,可以回撥進行重新整理和進行操作等! 直接上程式碼了! package com.***.fb**.widget; import android.content.Context; import
Android滑動回彈效果
原理: addHeaderView裡做的事: 1.測量出header的寬高,呼叫了measureView方法 2.設定LayoutParams,寬:MATCH_PARENT,高:10 3.設定topMargin的值為負的header的高度,即將header隱藏在螢幕最上方