Android 仿淘寶、京東商品詳情頁向上拖動查看圖文詳情控件DEMO詳解
一、淘寶商品詳情頁效果

我們的效果

二、實現(xiàn)思路
使用兩個scrollView,兩個scrollView 豎直排列,通過自定義viewGroup來控制兩個scrollView的豎直排列,以及滑動事件的處理。如下圖
三、具體實現(xiàn)
1、繼承viewGroup自定義布局View 重寫onMeasure()和onLayout方法,在onLayout方法中完成對兩個子ScrollView的豎直排列布局,代碼如下:
布局文件:
<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"
tools:context="com.baoyunlong.view.pulluptoloadmore.MainActivity">
<com.baoyunlong.view.pulluptoloadmore.PullUpToLoadMore
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.baoyunlong.view.pulluptoloadmore.MyScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:scaleType="fitXY"
android:src="@drawable/a1"
android:layout_width="match_parent"
android:layout_height="180dp" />
<TextView
android:text="這里是標(biāo)題"
android:textSize="18dp"
android:layout_marginRight="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_marginTop="10dp"
android:text="子標(biāo)題"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:textSize="18dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
..............
<LinearLayout
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="bottom"
android:layout_width="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:height="50dp"
android:background="#b11"
android:gravity="center"
android:text="繼續(xù)拖動查看圖文詳情"
android:textColor="#000" />
</LinearLayout>
</LinearLayout>
</com.baoyunlong.view.pulluptoloadmore.MyScrollView>
<com.baoyunlong.view.pulluptoloadmore.MyScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/a1" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/a3" />
.........
</LinearLayout>
</com.baoyunlong.view.pulluptoloadmore.MyScrollView> </com.baoyunlong.view.pulluptoloadmore.PullUpToLoadMore>
</RelativeLayout>
代碼:
public class PullUpToLoadMore extends ViewGroup {
public PullUpToLoadMore(Context context) {
super(context);
}
public PullUpToLoadMore(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PullUpToLoadMore(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
int childTop = t;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
child.layout(l, childTop, r, childTop + child.getMeasuredHeight());
childTop += child.getMeasuredHeight();
}
}
}
2、處理滑動事件
規(guī)則如下 :
(1)、當(dāng)處于第一屏?xí)r 第一個ScrollView已經(jīng)滑動到底部并且滑動方向是往上滑動,這個時候滑動事件應(yīng)該交給父view處理也就是攔截事件讓onInterceptTouchEvent返回true.然后父view通過scrollBy()方法滾動,顯示出第二個scrollView。
(2)、當(dāng)處于第二屏?xí)r 第二個ScrollView已經(jīng)滑動到頂部并且滑動方向是往下滑動,這個時候滑動事件交給父view處理,根據(jù)滑動事件顯示出第一個ScrollView。
(3)、當(dāng)手指離開屏幕時,根據(jù)滑動速度來決定是回彈到第一個ScrollView還是第二個ScrollView,通過VelocityTracker來獲取滑動速度。
3、一些細(xì)節(jié)的處理
(1)、如果仔細(xì)看觀察淘寶的實現(xiàn)效果你會發(fā)現(xiàn),當(dāng)你滑動到剛剛看到 “繼續(xù)拖動,查看圖文詳情”的時候,手指抬起,然后再按下重新向上拖動你會發(fā)現(xiàn),第二頁并不會劃出來,而是停留在了“繼續(xù)拖動,查看圖文詳情”的底部,京東的效果也是一樣。這樣用戶體驗不太好,我們來優(yōu)化一下。其實通過查看ScrollView的源碼可以看出來,這是因為ScrollView類的onTouchEvent方法的默認(rèn)實現(xiàn),調(diào)用了parent.requestDisallowInterceptTouchEvent(true)方法 阻止了我們攔截事件,導(dǎo)致我們父view的onInterceptTouchEvent方法無法執(zhí)行,也就攔截不到事件,攔截不到事件我們的onTouchEvent就無法執(zhí)行,onTouchEvent無法執(zhí)行,我們寫在onTouchEvent里面的滾動邏輯就執(zhí)行不到了,導(dǎo)致了上面我們看到的劃不動的效果。解決方法就是,我們需要重寫dispatchTouchEvent()方法,防止子view干擾我們,這樣我們滑動的時候就可以一氣呵成了。代碼如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//防止子View禁止父view攔截事件
this.requestDisallowInterceptTouchEvent(false);
return super.dispatchTouchEvent(ev);
}
(2)、監(jiān)聽ScrollView滑動事件的問題
ScrollView沒有提供滾動事件的監(jiān)聽方法,也就沒法判斷是否滾動到了頂部,或者底部,這里我們繼承ScrollView 自己實現(xiàn)滾動事件監(jiān)聽。
/**
* Created by baoyunlong on 16/6/8.
*/
public class MyScrollView extends ScrollView {
private static String TAG=MyScrollView.class.getName();
public void setScrollListener(ScrollListener scrollListener) {
this.mScrollListener = scrollListener;
}
private ScrollListener mScrollListener;
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_MOVE:
if(mScrollListener!=null){
int contentHeight=getChildAt(0).getHeight();
int scrollHeight=getHeight();
int scrollY=getScrollY();
mScrollListener.onScroll(scrollY);
if(scrollY+scrollHeight>=contentHeight||contentHeight<=scrollHeight){
mScrollListener.onScrollToBottom();
}else {
mScrollListener.notBottom();
}
if(scrollY==0){
mScrollListener.onScrollToTop();
}
}
break;
}
boolean result=super.onTouchEvent(ev);
requestDisallowInterceptTouchEvent(false);
return result;
}
public interface ScrollListener{
void onScrollToBottom();
void onScrollToTop();
void onScroll(int scrollY);
void notBottom();
}
4、完整代碼如下
/**
* Created by baoyunlong on 16/6/8.
*/
public class PullUpToLoadMore extends ViewGroup {
public static String TAG = PullUpToLoadMore.class.getName();
MyScrollView topScrollView, bottomScrollView;
VelocityTracker velocityTracker = VelocityTracker.obtain();
Scroller scroller = new Scroller(getContext());
int currPosition = 0;
int position1Y;
int lastY;
public int scaledTouchSlop;//最小滑動距離
int speed = 200;
boolean isIntercept;
public boolean bottomScrollVIewIsInTop = false;
public boolean topScrollViewIsBottom = false;
public PullUpToLoadMore(Context context) {
super(context);
init();
}
public PullUpToLoadMore(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PullUpToLoadMore(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
post(new Runnable() {
@Override
public void run() {
topScrollView = (MyScrollView) getChildAt(0);
bottomScrollView = (MyScrollView) getChildAt(1);
topScrollView.setScrollListener(new MyScrollView.ScrollListener() {
@Override
public void onScrollToBottom() {
topScrollViewIsBottom = true;
}
@Override
public void onScrollToTop() {
}
@Override
public void onScroll(int scrollY) {
}
@Override
public void notBottom() {
topScrollViewIsBottom = false;
}
});
bottomScrollView.setScrollListener(new MyScrollView.ScrollListener() {
@Override
public void onScrollToBottom() {
}
@Override
public void onScrollToTop() {
}
@Override
public void onScroll(int scrollY) {
if (scrollY == 0) {
bottomScrollVIewIsInTop = true;
} else {
bottomScrollVIewIsInTop = false;
}
}
@Override
public void notBottom() {
}
});
position1Y = topScrollView.getBottom();
scaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
});
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//防止子View禁止父view攔截事件
this.requestDisallowInterceptTouchEvent(false);
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
//判斷是否已經(jīng)滾動到了底部
if (topScrollViewIsBottom) {
int dy = lastY - y;
//判斷是否是向上滑動和是否在第一屏
if (dy > 0 && currPosition == 0) {
if (dy >= scaledTouchSlop) {
isIntercept = true;//攔截事件
lastY=y;
}
}
}
if (bottomScrollVIewIsInTop) {
int dy = lastY - y;
//判斷是否是向下滑動和是否在第二屏
if (dy < 0 && currPosition == 1) {
if (Math.abs(dy) >= scaledTouchSlop) {
isIntercept = true;
}
}
}
break;
}
return isIntercept;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int y = (int) event.getY();
velocityTracker.addMovement(event);
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
int dy = lastY - y;
if (getScrollY() + dy < 0) {
dy = getScrollY() + dy + Math.abs(getScrollY() + dy);
}
if (getScrollY() + dy + getHeight() > bottomScrollView.getBottom()) {
dy = dy - (getScrollY() + dy - (bottomScrollView.getBottom() - getHeight()));
}
scrollBy(0, dy);
break;
case MotionEvent.ACTION_UP:
isIntercept = false;
velocityTracker.computeCurrentVelocity(1000);
float yVelocity = velocityTracker.getYVelocity();
if (currPosition == 0) {
if (yVelocity < 0 && yVelocity < -speed) {
smoothScroll(position1Y);
currPosition = 1;
} else {
smoothScroll(0);
}
} else {
if (yVelocity > 0 && yVelocity > speed) {
smoothScroll(0);
currPosition = 0;
} else {
smoothScroll(position1Y);
}
}
break;
}
lastY = y;
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
int childTop = t;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
child.layout(l, childTop, r, childTop + child.getMeasuredHeight());
childTop += child.getMeasuredHeight();
}
}
//通過Scroller實現(xiàn)彈性滑動
private void smoothScroll(int tartY) {
int dy = tartY - getScrollY();
scroller.startScroll(getScrollX(), getScrollY(), 0, dy);
invalidate();
}
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
postInvalidate();
}
}
}
源碼:
以上所述是小編給大家介紹的Android 仿淘寶、京東商品詳情頁向上拖動查看圖文詳情控件DEMO詳解,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
- android 添加隨意拖動的桌面懸浮窗口
- Android 可拖動的seekbar自定義進(jìn)度值
- android 應(yīng)用內(nèi)部懸浮可拖動按鈕簡單實現(xiàn)代碼
- android Matrix實現(xiàn)圖片隨意放大縮小或拖動
- Android 實現(xiàn)可任意拖動的懸浮窗功能(類似懸浮球)
- Android實現(xiàn)ImageView圖片縮放和拖動
- Android實現(xiàn)跟隨手指拖動并自動貼邊的View樣式(實例demo)
- Android編程之控件可拖動的實現(xiàn)方法
- Android自定義垂直拖動seekbar進(jìn)度條
- Android自定義View實現(xiàn)拖動自動吸邊效果
相關(guān)文章
Android中App字體大小不隨系統(tǒng)改變而改變
這篇文章主要介紹了Android中App字體大小不隨系統(tǒng)改變而改變,需要的朋友可以參考下2017-04-04
Android 快速實現(xiàn)狀態(tài)欄透明樣式的示例代碼
下面小編就為大家分享一篇Android 快速實現(xiàn)狀態(tài)欄透明樣式的示例代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01
Android+Flutter實現(xiàn)彩虹圖案的繪制
彩虹,是氣象中的一種光學(xué)現(xiàn)象,當(dāng)太陽光照射到半空中的水滴,光線被折射及反射,在天空上形成拱形的七彩光譜。接下來,我們就自己手動繪制一下彩虹圖案吧2022-11-11
Kotlin示例講解標(biāo)準(zhǔn)函數(shù)with與run和apply的使用
Kotlin的標(biāo)準(zhǔn)函數(shù)是指 Standard.kt 文件中定義的函數(shù),任何Kotlin代碼都可以自由地調(diào)用所有的標(biāo)準(zhǔn)函數(shù)。文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-08-08
關(guān)于androidstuio導(dǎo)入系統(tǒng)源碼的問題
小編最近在做系統(tǒng)源碼導(dǎo)出來的小項目,在導(dǎo)入androidstudio過程中遇到過一些問題,本文以Schedule power on off為例給大家詳細(xì)介紹,需要的朋友參考下吧2021-06-06

