本文介绍一个好多App都有的布局容器,如图
这种效果在微博,美团,点评上面都有使用,是一种很不错的交互方式。
实现原理:
自定义一个Layout,可以是LinearLayout,RelativeLayout
容器总共有三个部分,HearderLayout最上面的部分,FloatLayout滑动的时候浮动的部分,ContentLayout下面的内容部分,这里我们使用了一个ViewPager+Fragment来代替,以保证满足使用时候的更多可能性。
在初始化容器的时候给容器测算大小,关键是在ContentLayout的大小,ContentLayout的高度是父容器的高度减去FloatLayout的高度,也就是向上滑动的时候当HeaderLayout完全滑出父控件之后,此时的Contentayout的高度加上FloatLayout的高度正好等于父容器的高度
在滑动的时候做事件分发和拦截,主要是处理什么时候滑动内部的ListView或者ScrollView,又在什么时候滑动整个容器。
当HeaderLayout没有完全隐藏的时候就滑动整个容器,当HeaderLayout隐藏的时候滑动内部的ListView或者ScrollView,
当向上滚动的时候,HeaderLayout完全隐藏时,整个容器就不再滚动了,接下来滚动的是ContentLayout的内容,所以就造成了FloatLayout悬浮在顶部的效果。
滚动重写了父容器的scrollTo来保证容器滚动的范围,滚动的范围在整个容器减掉HeaderLayout的高度的范围之内,不能太上,也不能太下。
FloatLayout.java
/** * 自定义的有悬浮layout的容器,类似微博,美团,点评的效果 * * @author mingwei * */ public class FloatLayout extends LinearLayout { private RelativeLayout mHeaderLayout; private LinearLayout mFloatLayout; private ViewPager mContent; private int mHeaderHeight; private boolean isHeaderHidden; private ViewGroup mInnerScrollview; private OverScroller mScroller; private VelocityTracker mVelocityTracker; private int mTouchSlop; private int mMaximumVelocity, mMinimumVelocity; private float mLastY; private boolean isDragging; private boolean isMove = false; public FloatLayout(Context context) { this(context, null); } public FloatLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FloatLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mScroller = new OverScroller(context); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mMaximumVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity(); mMinimumVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity(); } @Override protected void onFinishInflate() { super.onFinishInflate(); mHeaderLayout = (RelativeLayout) findViewById(R.id.float_layout_top); mFloatLayout = (LinearLayout) findViewById(R.id.float_layout_float); mContent = (ViewPager) findViewById(R.id.float_layout_content); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); ViewGroup.LayoutParams layoutParams = mContent.getLayoutParams(); layoutParams.height = getMeasuredHeight() - mFloatLayout.getMeasuredHeight(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mHeaderHeight = mHeaderLayout.getMeasuredHeight(); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { int action = ev.getAction(); float y = ev.getY(); switch (action) { case MotionEvent.ACTION_DOWN: mLastY = y; break; case MotionEvent.ACTION_MOVE: float moveY = y - mLastY; getCurrentScrollView(); if (mInnerScrollview instanceof ScrollView) { if (mInnerScrollview.getScrollY() == 0 && isHeaderHidden && moveY > 0 && !isMove) { isMove = true; return dispatchInnerChild(ev); } } else if (mInnerScrollview instanceof ListView) { ListView listView = (ListView) mInnerScrollview; View viewItem = listView.getChildAt(listView.getFirstVisiblePosition()); if (viewItem != null && viewItem.getTop() == 0 && isHeaderHidden && moveY > 0 && !isMove) { isMove = true; return dispatchInnerChild(ev); } } break; } return super.dispatchTouchEvent(ev); } private boolean dispatchInnerChild(MotionEvent ev) { ev.setAction(MotionEvent.ACTION_CANCEL); MotionEvent newMotionEvent = MotionEvent.obtain(ev); dispatchTouchEvent(ev); newMotionEvent.setAction(MotionEvent.ACTION_DOWN); return dispatchTouchEvent(newMotionEvent); } /** * 事件拦截,来处理什么时候应该滑动那个部分的容器 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction(); float y = ev.getY(); switch (action) { case MotionEvent.ACTION_DOWN: mLastY = y; break; case MotionEvent.ACTION_MOVE: float moveY = y - mLastY; getCurrentScrollView(); if (Math.abs(moveY) > mTouchSlop) { isDragging = true; if (mInnerScrollview instanceof ScrollView) { if (!isHeaderHidden || (mInnerScrollview.getScrollY() == 0 && isHeaderHidden && moveY > 0)) { initVelocityTracker(); mVelocityTracker.addMovement(ev); mLastY = y; return true; } } else if (mInnerScrollview instanceof ListView) { ListView listView = (ListView) mInnerScrollview; View viewItem = listView.getChildAt(listView.getFirstVisiblePosition()); if (!isHeaderHidden || (viewItem != null && viewItem.getTop() == 0 && moveY > 0)) { initVelocityTracker(); mVelocityTracker.addMovement(ev); mLastY = y; return true; } } } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: isDragging = false; recycleVelocityTracker(); break; default: break; } return super.onInterceptTouchEvent(ev); } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent event) { initVelocityTracker(); mVelocityTracker.addMovement(event); int action = event.getAction(); float y = event.getY(); switch (action) { case MotionEvent.ACTION_DOWN: if (!mScroller.isFinished()) { mScroller.abortAnimation(); } mLastY = y; return true; case MotionEvent.ACTION_MOVE: float moveY = y - mLastY; if (!isDragging && Math.abs(moveY) > mTouchSlop) { isDragging = true; } if (isDragging) { scrollBy(0, (int) -moveY); } mLastY = y; break; case MotionEvent.ACTION_CANCEL: isDragging = false; recycleVelocityTracker(); if (!mScroller.isFinished()) { mScroller.abortAnimation(); } break; case MotionEvent.ACTION_UP: isDragging = false; mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int velocity = (int) mVelocityTracker.getYVelocity(); if (Math.abs(velocity) > mMinimumVelocity) { fling(-velocity); } recycleVelocityTracker(); break; default: break; } return super.onTouchEvent(event); } /** * 重写scrollTo,用来控制在滚动的过程中不至于 超出范围. * * y<0,当Header完全显示在父容器时就不再允许Header能继续滑动. * * y>mHeaderHeight,当Header部分完全画出父控件时,y能到达的最大值就是就是Header的高度. * * y!=getScrollY(),调用父类的scrollTo,当y发生变化时,调用父类scrollTo滚动. */ @Override public void scrollTo(int x, int y) { y = (y < 0) ? 0 : y; y = (y > mHeaderHeight) ? mHeaderHeight : y; if (y != getScrollY()) { super.scrollTo(x, y); } isHeaderHidden = getScrollY() == mHeaderHeight; } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(0, mScroller.getCurrY()); invalidate(); } } /** * 容器滚动时松开手指后根据velocity自动滚到到指定位置 * * @param velocityY * 松开时的速度,OverScroll类帮助我们计算要滑多远 */ public void fling(int velocityY) { mScroller.fling(0, getScrollY(), 0, velocityY, 0, 0, 0, mHeaderHeight); invalidate(); } /** * 根据当前的View来处理事件分发,例如容器当中是ScrollView,或者ListView时 */ private void getCurrentScrollView() { int cuttentItem = mContent.getCurrentItem(); PagerAdapter pagerAdapter = mContent.getAdapter(); if (pagerAdapter instanceof FragmentPagerAdapter) { FragmentPagerAdapter adapter = (FragmentPagerAdapter) pagerAdapter; Fragment fragment = adapter.getItem(cuttentItem); mInnerScrollview = (ViewGroup) fragment.getView().findViewById(R.id.float_layout_inner_view); } else if (pagerAdapter instanceof FragmentStatePagerAdapter) { FragmentStatePagerAdapter adapter = (FragmentStatePagerAdapter) pagerAdapter; Fragment fragment = adapter.getItem(cuttentItem); mInnerScrollview = (ViewGroup) fragment.getView().findViewById(R.id.float_layout_inner_view); } } /** * 初始化VelocityTracker */ private void initVelocityTracker() { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } } /** * 回收VelocityTracker */ private void recycleVelocityTracker() { if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } } }
ListViewFragment.java
public class ListViewFragment extends Fragment { private View mContentView; private ListView mListView; private List<String> mList = new ArrayList<String>(); public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mContentView = LayoutInflater.from(getActivity()).inflate(R.layout.float_layout_inner_listview, null); mListView = (ListView) mContentView.findViewById(R.id.float_layout_inner_view); initData(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return mContentView; } private void initData() { for (int i = 0; i < 100; i++) { mList.add("ListView_Item" + i); } mListView.setAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mList)); } static Fragment getInstain() { Fragment fragment = new ListViewFragment(); return fragment; } }xml
<?xml version="1.0" encoding="utf-8"?> <ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@id/float_layout_inner_view" android:layout_width="match_parent" android:layout_height="match_parent" > </ListView>
public class ScrollViewFragment extends Fragment { private View mContentView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mContentView = LayoutInflater.from(getActivity()).inflate(R.layout.float_layout_inner_scrollview, null); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return mContentView; } static Fragment getInstain() { Fragment fragment = new ScrollViewFragment(); return fragment; } }xml
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@id/float_layout_inner_view" android:layout_width="match_parent" android:layout_height="wrap_content" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > 放20个TextView看效果 <TextView android:layout_width="match_parent" android:layout_height="50dip" android:gravity="center" android:text="@string/float_layout_test_1" /> <TextView android:layout_width="match_parent" android:layout_height="50dip" android:gravity="center" android:text="@string/float_layout_test_1" /> <TextView android:layout_width="match_parent" android:layout_height="50dip" android:gravity="center" android:text="@string/float_layout_test_1" /> </LinearLayout> </ScrollView>
MainActivity.java
public class MainActivity extends FragmentActivity { private ViewPager mFloatContent; private List<Fragment> mFragments = new ArrayList<Fragment>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } private void initView() { mFloatContent = (ViewPager) findViewById(R.id.float_layout_content); mFragments.add(ListViewFragment.getInstain()); mFragments.add(ScrollViewFragment.getInstain()); mFloatContent.setAdapter(new MyAdapter(getSupportFragmentManager(), mFragments)); } class MyAdapter extends FragmentPagerAdapter { private List<Fragment> mList; public MyAdapter(FragmentManager fm, List<Fragment> list) { super(fm); mList = list; } @Override public Fragment getItem(int arg0) { return mList.get(arg0); } @Override public int getCount() { return mList.size(); } } }xml
<com.mingwei.floatlayout.FloatLayout 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" android:orientation="vertical" > <RelativeLayout android:id="@+id/float_layout_top" android:layout_width="match_parent" android:layout_height="200dip" android:background="@android:color/holo_blue_bright" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="@string/float_top_layout_text" /> </RelativeLayout> <LinearLayout android:id="@+id/float_layout_float" android:layout_width="match_parent" android:layout_height="50dip" android:background="@android:color/holo_green_light" android:orientation="horizontal" > <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="@string/float_layout_test_listview" /> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="@string/float_layout_test_scrollview" /> </LinearLayout> <android.support.v4.view.ViewPager android:id="@id/float_layout_content" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/holo_orange_light" > </android.support.v4.view.ViewPager> </com.mingwei.floatlayout.FloatLayout>
Github:https://github.com/Mingwei360/FloatLayout