最近做了个个人中心的页面,上面部分显示头像昵称,标签,以及关注数和粉丝数,下面部分显示发表的帖子列表,下面部分是用H5做的,是带着下拉刷新上拉加载功能的列表页,要求向上滑动时,上面信息部分和下面的帖子列表部分要整体滑动,当信息部分滑动到不可见时,下面的帖子列表要也能上下滑动,并能上拉加载。
直接用scrollview的话,不管怎么自定义,要么是上下可以整体滑动,但上拉加载下拉刷新的功能不可用,要么就是滑动卡得要命。怎么都不好用。
在网上查了几天的资料,换了好几种方法终于搞定。
效果:
具体实现如下:
利用DragTopLayout这个开源控件可以轻松实现:
代码:
package com.h.fileinput.widget; import android.content.Context; import android.graphics.PointF; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.widget.AbsListView; import android.widget.FrameLayout; import android.widget.Scroller; /** * Created by Administrator on 2017/6/6 0006. * 上下整体滑动的布局 */ public class DragTopLayout extends FrameLayout { public DragTopLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } public DragTopLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DragTopLayout(Context context) { this(context, null); } private void init() { mScroller = new Scroller(getContext().getApplicationContext()); } /** * 当前头部可见还是滚出屏幕,true为滚出屏幕 */ private boolean mCollapsed = false; /** * 头部高度 */ private int mHeadHeight; /** * 滚动辅助 */ private Scroller mScroller; /** * 速度计算,每次用完要recycle */ private VelocityTracker velocityTracker; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); measureChildren(widthMeasureSpec, heightMeasureSpec); mHeadHeight = getChildAt(0).getMeasuredHeight(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { View v1 = getChildAt(0); v1.layout(0, 0, v1.getMeasuredWidth(), v1.getMeasuredHeight()); View v2 = getChildAt(1); v2.layout(0, v1.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight() + mHeadHeight); } /** * 按下点,滑动过程中的上一个点 */ private PointF mDownPoint = new PointF(); @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mDownPoint.set(event.getX(), event.getY()); break; case MotionEvent.ACTION_MOVE: float y = event.getY(); // Y方向需要scrollBy的距离,正值表示需要向上滚动 float scrollByDelt = -y + mDownPoint.y; // 假如按照计算的距离偏移后的偏移量,即getScrollY()的值 float realDelt = getScrollY() + scrollByDelt; Log.e("lzw", "------> " + scrollByDelt + " " + realDelt); if (realDelt < 0) { // 表示按照实际的手指滑动距离向下移动的话太大,则直接计算出刚好头部显示出来的realDelt scrollByDelt = 0 - getScrollY(); } else if (realDelt > mHeadHeight) { // 同样表示实际距离太大,计算出合适的距离 scrollByDelt = mHeadHeight - getScrollY(); } scrollBy(0, (int) scrollByDelt); mDownPoint.set(event.getX(), y); break; case MotionEvent.ACTION_UP: mDownPoint.set(0, 0); checkPosition(); break; } return true; } @Override public boolean onInterceptTouchEvent(MotionEvent event) { Log.e("lzw", "onInterceptTouchEvent canChildScrollUp " + canChildScrollUp()); if (canChildScrollUp()) { // 能向上滚动,一定是滚动view处理事件 return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mDownPoint.set(event.getX(), event.getY()); return false; case MotionEvent.ACTION_MOVE: // 横向滚动大于纵向,也不响应 if (Math.abs(event.getX() - mDownPoint.x) > Math.abs(event.getY() - mDownPoint.y)) { mDownPoint.set(event.getX(), event.getY()); return super.onInterceptTouchEvent(event); } // 在向下移动 if (event.getY() > mDownPoint.y) { mDownPoint.set(event.getX(), event.getY()); if (mCollapsed) { // 头部不可见了,向下滚动需要拦截 return true; } else { return super.onInterceptTouchEvent(event); } } // 在向上移动 if (event.getY() < mDownPoint.y) { mDownPoint.set(event.getX(), event.getY()); if (mCollapsed) { // 头部滚出屏幕,不拦截 return super.onInterceptTouchEvent(event); } else { return true; } } mDownPoint.set(event.getX(), event.getY()); case MotionEvent.ACTION_UP: // 检查头部是否移除去 mCollapsed = getScrollY() >= mHeadHeight; mDownPoint.set(event.getX(), event.getY()); return super.onInterceptTouchEvent(event); } return true; } @Override public void requestDisallowInterceptTouchEvent(boolean b) { // if this is a List < L or another view that doesn't support nested // scrolling, ignore this request so that the vertical scroll event // isn't stolen if ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView) || (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) { // Nope. } else { super.requestDisallowInterceptTouchEvent(b); } } /** * @return Whether it is possible for the child view of this layout to * scroll up. Override this if the child view is a custom view. */ public boolean canChildScrollUp() { if (android.os.Build.VERSION.SDK_INT < 14) { if (mTarget instanceof AbsListView) { final AbsListView absListView = (AbsListView) mTarget; return absListView.getChildCount() > 0 && (absListView.getFirstVisiblePosition() > 0 || absListView .getChildAt(0).getTop() < absListView .getPaddingTop()); } else { return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0; } } else { return ViewCompat.canScrollVertically(mTarget, -1); } } /** * 检查是否需要关闭或者打开 */ private void checkPosition() { // 移出去的大小,不能直接在if里面用,否则返回值不正确 int opOffset = getScrollY(); if (opOffset < (mHeadHeight / 2)) { open(); } else { close(); } } /** * 向上移动,隐藏头部 */ private void close() { mCollapsed = true; mScroller.startScroll(0, getScrollY(), 0, mHeadHeight - getScrollY()); invalidate(); } /** * 向下移动,头部出现 */ private void open() { mCollapsed = false; mScroller.startScroll(0, getScrollY(), 0, -getScrollY()); invalidate(); } @Override public void computeScroll() { // 返回值为boolean,true说明滚动尚未完成,false说明滚动已经完成。 if (mScroller.computeScrollOffset()) { // 移动位置 scrollTo(0, mScroller.getCurrY()); invalidate(); } } /** * 可滚动view */ private View mTarget; /** * 设置可滚动view */ public void setTargetView(View v) { mTarget = v; } }
布局文件:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.h.fileinput.widget.DragTopLayout android:id="@+id/dragLayout" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:id="@+id/top_view" android:layout_width="match_parent" android:layout_height="220dp" android:background="@color/cardview_dark_background" android:gravity="center" android:orientation="vertical"> <RelativeLayout android:layout_marginTop="13dp" android:layout_above="@+id/rl_tagHeader" android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:id="@+id/btn_otherLeft" android:visibility="gone" android:layout_width="22dp" android:layout_height="22dp" android:layout_centerVertical="true" android:layout_marginLeft="15dp" android:background="@drawable/icon_back" /> <TextView android:id="@+id/tv_userNick" android:text="未命名" android:textSize="16sp" android:textColor="@color/text_white" android:layout_centerHorizontal="true" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout> <RelativeLayout android:id="@+id/rl_tagHeader" android:layout_marginTop="10dp" android:layout_width="match_parent" android:layout_height="wrap_content"> <com.h.fileinput.widget.CircleView android:id="@+id/pUserHeader" android:layout_centerHorizontal="true" android:src="@drawable/zhongyuan" android:layout_width="92dp" android:layout_height="92dp" /> </RelativeLayout> <ImageView android:id="@+id/tv_follow" android:layout_marginTop="5dp" android:src="@drawable/icon_add_follow" android:layout_gravity="center_horizontal" android:layout_width="42dp" android:layout_height="14dp" /> <LinearLayout android:id="@+id/ll_summaryBottom" android:orientation="horizontal" android:layout_marginTop="10dp" android:gravity="center" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:orientation="vertical" android:gravity="center_horizontal" android:layout_width="100dp" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_followNum" android:text="0" style="@style/person_num" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:text="关注" style="@style/person_text1" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <View android:background="@color/text_white" android:layout_marginBottom="5dp" android:layout_marginTop="5dp" android:layout_width="1dp" android:layout_height="30dp"></View> <LinearLayout android:orientation="vertical" android:gravity="center_horizontal" android:layout_width="100dp" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_fansNum" android:text="0" style="@style/person_num" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:text="粉丝" style="@style/person_text1" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout> </LinearLayout> <WebView android:id="@+id/myWebview" android:layout_width="match_parent" android:layout_height="match_parent"> </WebView> </com.h.fileinput.widget.DragTopLayout> </RelativeLayout>
页面实现:
import android.app.Activity; import android.os.Build; import android.os.Bundle; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.LinearLayout; import com.h.fileinput.R; import com.h.fileinput.widget.DragTopLayout; /* * 类似于个人中心页,下方也有滑动,要求上下两部分整体滑动的例子 * */ public class DragTopDemoActivity extends Activity { private DragTopLayout dragTopLayout; private LinearLayout top_view; private WebView myWebView; String path = "http://********/my.html?userId=201608251250230002&tuserId=26728010&phoneNumber=*******"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drag_top_demo); dragTopLayout = (DragTopLayout) findViewById(R.id.dragLayout); top_view = (LinearLayout) findViewById(R.id.top_view); myWebView = (WebView) findViewById(R.id.myWebview); dragTopLayout.setTargetView(myWebView); initWebView(path); } public boolean isWebViewAttach(WebView webView){ if (webView != null) { if (webView.getScrollY() > 0) { return false; } } return true; } public void initWebView(String path) { WebSettings settings = myWebView.getSettings(); settings.setJavaScriptEnabled(true); //设置可以访问文件 settings.setAllowFileAccess(true); //设置支持缩放 settings.setBuiltInZoomControls(true); //修改某些机型二维码显示不出来的问题 //打开DOM储存API settings.setDomStorageEnabled(true); settings.setAllowFileAccess(true); settings.setAppCacheEnabled(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); } //修改WebView页面图片大小 settings.setUseWideViewPort(true);//让webview读取网页设置的viewport,pc版网页 settings.setLoadWithOverviewMode(true); // settings.setTextSize(WebSettings.TextSize.NORMAL); 加载需要显示的网页 myWebView.loadUrl(path); //设置Web视图 myWebView.setWebViewClient(new MyWebViewClient()); } //Web视图 private class MyWebViewClient extends WebViewClient { public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } } }
注意:
1.个人信息部分一定要放在一个整体布局中,这样滑动时,DragTopLayout才能获取到个人信息的整体高度。同样,其余部分要放在另一个整体布局中。