本文介绍一个好多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;
- }
- }
- }
内容部分分别使用了Fragment去装一个ListView的和一个ScrollView去处理事件分发
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 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>
ScrollViewFragment.java
- 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 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>
在Activity中如何使用
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();
- }
- }
- }
- <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