이 글에서는 위로 드래그하여 그래픽 세부정보를 볼 수 있는 Android 모방 Taobao 및 JD.com 제품 세부정보 페이지에 대한 자세한 DEMO 설명을 소개하며, 두 개의 scrollView가 수직 위치로 배열됩니다. 두 개의 scrollView는 viewGroup을 사용자 정의하고 슬라이딩 이벤트를 처리하여 제어됩니다. 안드로이드에서 사진과 텍스트 내용을 드래그해서 보는 지식에 관심있는 친구들은 함께 배워보세요
1. 타오바오 상품상세페이지 효과
우리의 효과
2. 구현 아이디어
두 개의 scrollView, 두 개의 scrollView가 수직으로 배열되고, 두 개의 scrollView의 수직 배열은 viewGroup 사용자 정의 및 슬라이딩 이벤트 처리를 통해 제어됩니다. 아래와 같이
3. 특정 구현
1. viewGroup 사용자 정의 레이아웃을 상속하고 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="这里是标题" 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="子标题" 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="继续拖动查看图文详情" 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. 슬라이딩 이벤트 처리
규칙은 다음과 같습니다.
(1 ) 첫 번째 화면에서 첫 번째 ScrollView가 아래쪽으로 미끄러지고 슬라이딩 방향이 위쪽인 경우 이때 슬라이딩 이벤트를 상위 뷰로 넘겨 처리해야 합니다. 즉, 이벤트를 가로채고 onInterceptTouchEvent가 반환되도록 해야 합니다. true 그런 다음 상위 뷰는 scrollBy() 메서드를 스크롤하여 두 번째 scrollView를 표시합니다.
(2) 두 번째 화면에서는 두 번째 ScrollView가 위로 미끄러지고 슬라이딩 방향이 아래로 미끄러지는 상태에서 슬라이딩 이벤트가 처리를 위해 상위 뷰로 넘겨집니다. 첫 번째는 슬라이딩 이벤트에 따라 표시됩니다.
(3) 손가락이 화면을 떠날 때 슬라이딩 속도에 따라 첫 번째 ScrollView로 리바운드할지 두 번째 ScrollView로 리바운드할지 결정하고 VelocityTracker를 통해 슬라이딩 속도를 구합니다.
3. 일부 세부정보 처리
(1) 타오바오 구현 효과를 잘 관찰해보면 방금 본 지점으로 슬라이드하면 '계속 드래그하세요. 그림과 텍스트의 세부 사항 " "을 누른 다음 다시 누르고 위쪽으로 드래그하면 두 번째 페이지가 그려지지 않고 "계속 드래그하여 그림 및 텍스트 세부 정보를 볼 수 있습니다"의 하단에 유지됩니다. ". JD.com의 효과도 마찬가지다. 이 사용자 경험은 그다지 좋지 않습니다. 최적화해 보겠습니다. 실제로 ScrollView의 소스 코드를 보면 이는 ScrollView 클래스의 onTouchEvent 메서드의 기본 구현이 이벤트를 가로채는 것을 방지하기 위해 parent.requestDisallowInterceptTouchEvent(true) 메서드를 호출하여 onInterceptTouchEvent가 발생하기 때문임을 알 수 있습니다. 이벤트를 수신할 수 없거나 이벤트를 차단할 수 없으면 onTouchEvent를 실행할 수 없으므로 onTouchEvent에 작성된 스크롤 로직을 실행할 수 없습니다. 위에서 본 것처럼 스크롤 효과가 불가능합니다. 해결책은 하위 뷰가 우리를 방해하지 않도록 dispatchTouchEvent() 메서드를 다시 작성하여 슬라이드할 때 한 번에 수행할 수 있도록 해야 한다는 것입니다. 코드는 다음과 같습니다.
@Override public boolean dispatchTouchEvent(MotionEvent ev) { //防止子View禁止父view拦截事件 this.requestDisallowInterceptTouchEvent(false); return super.dispatchTouchEvent(ev); }
(2) ScrollView의 슬라이딩 이벤트 모니터링 문제
ScrollView는 모니터링 방법을 제공하지 않습니다. 스크롤 이벤트의 경우 위쪽으로 스크롤했는지 아래쪽으로 스크롤했는지 확인할 방법이 없습니다. 여기서는 ScrollView를 상속하고 스크롤 이벤트 모니터링을 직접 구현합니다.
/** * 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: //判断是否已经滚动到了底部 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实现弹性滑动 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(); } } }
출처 code:
github 주소
위는 에디터가 소개하는 안드로이드 모방 타오바오, JD.com 제품 상세 페이지에 대한 자세한 설명입니다. 위로 드래그하시면 그래픽을 보실 수 있습니다. 그리고 텍스트 세부 사항은 DEMO를 제어하는 데 도움이 되기를 바랍니다. 질문이 있는 경우 메시지를 남겨주시면 편집자가 제 시간에 답변해 드리겠습니다. 또한 PHP 중국어 웹사이트를 지원해 주신 모든 분들께 감사드립니다!
더 많은 Android 모방 Taobao 및 JD.com 제품 세부 정보 페이지를 보려면 위로 드래그하여 그래픽 세부 정보를 확인하고 DEMO를 제어하여 자세한 설명을 보려면 PHP 중국어 웹사이트를 주목하세요!