Android自定义控件——FloatLayout

本文介绍一个好多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

  1. /** 
  2.  * 自定义的有悬浮layout的容器,类似微博,美团,点评的效果 
  3.  *  
  4.  * @author mingwei 
  5.  *  
  6.  */  
  7. public class FloatLayout extends LinearLayout {  
  8.   
  9.     private RelativeLayout mHeaderLayout;  
  10.     private LinearLayout mFloatLayout;  
  11.     private ViewPager mContent;  
  12.   
  13.     private int mHeaderHeight;  
  14.     private boolean isHeaderHidden;  
  15.     private ViewGroup mInnerScrollview;  
  16.   
  17.     private OverScroller mScroller;  
  18.     private VelocityTracker mVelocityTracker;  
  19.     private int mTouchSlop;  
  20.     private int mMaximumVelocity, mMinimumVelocity;  
  21.   
  22.     private float mLastY;  
  23.     private boolean isDragging;  
  24.     private boolean isMove = false;  
  25.   
  26.     public FloatLayout(Context context) {  
  27.         this(context, null);  
  28.     }  
  29.   
  30.     public FloatLayout(Context context, AttributeSet attrs) {  
  31.         this(context, attrs, 0);  
  32.     }  
  33.   
  34.     public FloatLayout(Context context, AttributeSet attrs, int defStyleAttr) {  
  35.         super(context, attrs, defStyleAttr);  
  36.         mScroller = new OverScroller(context);  
  37.         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();  
  38.         mMaximumVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();  
  39.         mMinimumVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();  
  40.   
  41.     }  
  42.   
  43.     @Override  
  44.     protected void onFinishInflate() {  
  45.         super.onFinishInflate();  
  46.         mHeaderLayout = (RelativeLayout) findViewById(R.id.float_layout_top);  
  47.         mFloatLayout = (LinearLayout) findViewById(R.id.float_layout_float);  
  48.         mContent = (ViewPager) findViewById(R.id.float_layout_content);  
  49.     }  
  50.   
  51.     @Override  
  52.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  53.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  54.         ViewGroup.LayoutParams layoutParams = mContent.getLayoutParams();  
  55.         layoutParams.height = getMeasuredHeight() - mFloatLayout.getMeasuredHeight();  
  56.     }  
  57.   
  58.     @Override  
  59.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  60.         super.onSizeChanged(w, h, oldw, oldh);  
  61.         mHeaderHeight = mHeaderLayout.getMeasuredHeight();  
  62.     }  
  63.   
  64.     @Override  
  65.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  66.         int action = ev.getAction();  
  67.         float y = ev.getY();  
  68.         switch (action) {  
  69.         case MotionEvent.ACTION_DOWN:  
  70.             mLastY = y;  
  71.             break;  
  72.         case MotionEvent.ACTION_MOVE:  
  73.             float moveY = y - mLastY;  
  74.             getCurrentScrollView();  
  75.             if (mInnerScrollview instanceof ScrollView) {  
  76.                 if (mInnerScrollview.getScrollY() == 0 && isHeaderHidden && moveY > 0 && !isMove) {  
  77.                     isMove = true;  
  78.                     return dispatchInnerChild(ev);  
  79.                 }  
  80.             } else if (mInnerScrollview instanceof ListView) {  
  81.                 ListView listView = (ListView) mInnerScrollview;  
  82.                 View viewItem = listView.getChildAt(listView.getFirstVisiblePosition());  
  83.                 if (viewItem != null && viewItem.getTop() == 0 && isHeaderHidden && moveY > 0 && !isMove) {  
  84.                     isMove = true;  
  85.                     return dispatchInnerChild(ev);  
  86.                 }  
  87.             }  
  88.             break;  
  89.         }  
  90.         return super.dispatchTouchEvent(ev);  
  91.     }  
  92.   
  93.     private boolean dispatchInnerChild(MotionEvent ev) {  
  94.         ev.setAction(MotionEvent.ACTION_CANCEL);  
  95.         MotionEvent newMotionEvent = MotionEvent.obtain(ev);  
  96.         dispatchTouchEvent(ev);  
  97.         newMotionEvent.setAction(MotionEvent.ACTION_DOWN);  
  98.         return dispatchTouchEvent(newMotionEvent);  
  99.     }  
  100.   
  101.     /** 
  102.      * 事件拦截,来处理什么时候应该滑动那个部分的容器 
  103.      */  
  104.     @Override  
  105.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  106.         int action = ev.getAction();  
  107.         float y = ev.getY();  
  108.         switch (action) {  
  109.         case MotionEvent.ACTION_DOWN:  
  110.             mLastY = y;  
  111.             break;  
  112.         case MotionEvent.ACTION_MOVE:  
  113.             float moveY = y - mLastY;  
  114.             getCurrentScrollView();  
  115.             if (Math.abs(moveY) > mTouchSlop) {  
  116.                 isDragging = true;  
  117.                 if (mInnerScrollview instanceof ScrollView) {  
  118.                     if (!isHeaderHidden || (mInnerScrollview.getScrollY() == 0 && isHeaderHidden && moveY > 0)) {  
  119.                         initVelocityTracker();  
  120.                         mVelocityTracker.addMovement(ev);  
  121.                         mLastY = y;  
  122.                         return true;  
  123.                     }  
  124.                 } else if (mInnerScrollview instanceof ListView) {  
  125.                     ListView listView = (ListView) mInnerScrollview;  
  126.                     View viewItem = listView.getChildAt(listView.getFirstVisiblePosition());  
  127.                     if (!isHeaderHidden || (viewItem != null && viewItem.getTop() == 0 && moveY > 0)) {  
  128.                         initVelocityTracker();  
  129.                         mVelocityTracker.addMovement(ev);  
  130.                         mLastY = y;  
  131.                         return true;  
  132.                     }  
  133.                 }  
  134.             }  
  135.   
  136.         case MotionEvent.ACTION_CANCEL:  
  137.   
  138.         case MotionEvent.ACTION_UP:  
  139.             isDragging = false;  
  140.             recycleVelocityTracker();  
  141.             break;  
  142.         default:  
  143.             break;  
  144.         }  
  145.         return super.onInterceptTouchEvent(ev);  
  146.     }  
  147.   
  148.     @SuppressLint("ClickableViewAccessibility")  
  149.     @Override  
  150.     public boolean onTouchEvent(MotionEvent event) {  
  151.         initVelocityTracker();  
  152.         mVelocityTracker.addMovement(event);  
  153.         int action = event.getAction();  
  154.         float y = event.getY();  
  155.         switch (action) {  
  156.         case MotionEvent.ACTION_DOWN:  
  157.             if (!mScroller.isFinished()) {  
  158.                 mScroller.abortAnimation();  
  159.             }  
  160.             mLastY = y;  
  161.             return true;  
  162.         case MotionEvent.ACTION_MOVE:  
  163.             float moveY = y - mLastY;  
  164.             if (!isDragging && Math.abs(moveY) > mTouchSlop) {  
  165.                 isDragging = true;  
  166.             }  
  167.             if (isDragging) {  
  168.                 scrollBy(0, (int) -moveY);  
  169.             }  
  170.             mLastY = y;  
  171.             break;  
  172.         case MotionEvent.ACTION_CANCEL:  
  173.             isDragging = false;  
  174.             recycleVelocityTracker();  
  175.             if (!mScroller.isFinished()) {  
  176.                 mScroller.abortAnimation();  
  177.             }  
  178.             break;  
  179.         case MotionEvent.ACTION_UP:  
  180.             isDragging = false;  
  181.             mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);  
  182.             int velocity = (int) mVelocityTracker.getYVelocity();  
  183.             if (Math.abs(velocity) > mMinimumVelocity) {  
  184.                 fling(-velocity);  
  185.             }  
  186.             recycleVelocityTracker();  
  187.             break;  
  188.   
  189.         default:  
  190.             break;  
  191.         }  
  192.         return super.onTouchEvent(event);  
  193.     }  
  194.   
  195.     /** 
  196.      * 重写scrollTo,用来控制在滚动的过程中不至于 超出范围. 
  197.      *  
  198.      * y<0,当Header完全显示在父容器时就不再允许Header能继续滑动. 
  199.      *  
  200.      * y>mHeaderHeight,当Header部分完全画出父控件时,y能到达的最大值就是就是Header的高度. 
  201.      *  
  202.      * y!=getScrollY(),调用父类的scrollTo,当y发生变化时,调用父类scrollTo滚动. 
  203.      */  
  204.     @Override  
  205.     public void scrollTo(int x, int y) {  
  206.         y = (y < 0) ? 0 : y;  
  207.         y = (y > mHeaderHeight) ? mHeaderHeight : y;  
  208.         if (y != getScrollY()) {  
  209.             super.scrollTo(x, y);  
  210.         }  
  211.         isHeaderHidden = getScrollY() == mHeaderHeight;  
  212.     }  
  213.   
  214.     @Override  
  215.     public void computeScroll() {  
  216.         if (mScroller.computeScrollOffset()) {  
  217.             scrollTo(0, mScroller.getCurrY());  
  218.             invalidate();  
  219.         }  
  220.     }  
  221.   
  222.     /** 
  223.      * 容器滚动时松开手指后根据velocity自动滚到到指定位置 
  224.      *  
  225.      * @param velocityY 
  226.      *            松开时的速度,OverScroll类帮助我们计算要滑多远 
  227.      */  
  228.     public void fling(int velocityY) {  
  229.         mScroller.fling(0, getScrollY(), 0, velocityY, 000, mHeaderHeight);  
  230.         invalidate();  
  231.     }  
  232.   
  233.     /** 
  234.      * 根据当前的View来处理事件分发,例如容器当中是ScrollView,或者ListView时 
  235.      */  
  236.     private void getCurrentScrollView() {  
  237.         int cuttentItem = mContent.getCurrentItem();  
  238.         PagerAdapter pagerAdapter = mContent.getAdapter();  
  239.         if (pagerAdapter instanceof FragmentPagerAdapter) {  
  240.             FragmentPagerAdapter adapter = (FragmentPagerAdapter) pagerAdapter;  
  241.             Fragment fragment = adapter.getItem(cuttentItem);  
  242.             mInnerScrollview = (ViewGroup) fragment.getView().findViewById(R.id.float_layout_inner_view);  
  243.         } else if (pagerAdapter instanceof FragmentStatePagerAdapter) {  
  244.             FragmentStatePagerAdapter adapter = (FragmentStatePagerAdapter) pagerAdapter;  
  245.             Fragment fragment = adapter.getItem(cuttentItem);  
  246.             mInnerScrollview = (ViewGroup) fragment.getView().findViewById(R.id.float_layout_inner_view);  
  247.         }  
  248.     }  
  249.   
  250.     /** 
  251.      * 初始化VelocityTracker 
  252.      */  
  253.     private void initVelocityTracker() {  
  254.         if (mVelocityTracker == null) {  
  255.             mVelocityTracker = VelocityTracker.obtain();  
  256.         }  
  257.     }  
  258.   
  259.     /** 
  260.      * 回收VelocityTracker 
  261.      */  
  262.     private void recycleVelocityTracker() {  
  263.         if (mVelocityTracker != null) {  
  264.             mVelocityTracker.recycle();  
  265.             mVelocityTracker = null;  
  266.         }  
  267.     }  
  268.   
  269. }  

内容部分分别使用了Fragment去装一个ListView的和一个ScrollView去处理事件分发

ListViewFragment.java

  1. public class ListViewFragment extends Fragment {  
  2.   
  3.     private View mContentView;  
  4.     private ListView mListView;  
  5.     private List<String> mList = new ArrayList<String>();  
  6.   
  7.     public void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         mContentView = LayoutInflater.from(getActivity()).inflate(R.layout.float_layout_inner_listview, null);  
  10.         mListView = (ListView) mContentView.findViewById(R.id.float_layout_inner_view);  
  11.         initData();  
  12.     }  
  13.   
  14.     @Override  
  15.     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
  16.         return mContentView;  
  17.     }  
  18.   
  19.     private void initData() {  
  20.         for (int i = 0; i < 100; i++) {  
  21.             mList.add("ListView_Item" + i);  
  22.         }  
  23.         mListView.setAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mList));  
  24.     }  
  25.   
  26.     static Fragment getInstain() {  
  27.         Fragment fragment = new ListViewFragment();  
  28.         return fragment;  
  29.     }  
  30. }  
xml

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <ListView xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:id="@id/float_layout_inner_view"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent" >  
  6.   
  7. </ListView>  

ScrollViewFragment.java

  1. public class ScrollViewFragment extends Fragment {  
  2.     private View mContentView;  
  3.   
  4.     @Override  
  5.     public void onCreate(Bundle savedInstanceState) {  
  6.         super.onCreate(savedInstanceState);  
  7.         mContentView = LayoutInflater.from(getActivity()).inflate(R.layout.float_layout_inner_scrollview, null);  
  8.     }  
  9.   
  10.     @Override  
  11.     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
  12.         return mContentView;  
  13.     }  
  14.   
  15.     static Fragment getInstain() {  
  16.         Fragment fragment = new ScrollViewFragment();  
  17.         return fragment;  
  18.     }  
  19. }  
xml

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:id="@id/float_layout_inner_view"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="wrap_content" >  
  6.   
  7.     <LinearLayout  
  8.         android:layout_width="match_parent"  
  9.         android:layout_height="wrap_content"  
  10.         android:orientation="vertical" >  
  11.   
  12.         放20个TextView看效果  
  13.   
  14.         <TextView  
  15.             android:layout_width="match_parent"  
  16.             android:layout_height="50dip"  
  17.             android:gravity="center"  
  18.             android:text="@string/float_layout_test_1" />  
  19.   
  20.         <TextView  
  21.             android:layout_width="match_parent"  
  22.             android:layout_height="50dip"  
  23.             android:gravity="center"  
  24.             android:text="@string/float_layout_test_1" />  
  25.   
  26.         <TextView  
  27.             android:layout_width="match_parent"  
  28.             android:layout_height="50dip"  
  29.             android:gravity="center"  
  30.             android:text="@string/float_layout_test_1" />  
  31.     </LinearLayout>  
  32.   
  33. </ScrollView>  

在Activity中如何使用

MainActivity.java

  1. public class MainActivity extends FragmentActivity {  
  2.   
  3.     private ViewPager mFloatContent;  
  4.     private List<Fragment> mFragments = new ArrayList<Fragment>();  
  5.   
  6.     @Override  
  7.     protected void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         setContentView(R.layout.activity_main);  
  10.         initView();  
  11.     }  
  12.   
  13.     private void initView() {  
  14.         mFloatContent = (ViewPager) findViewById(R.id.float_layout_content);  
  15.         mFragments.add(ListViewFragment.getInstain());  
  16.         mFragments.add(ScrollViewFragment.getInstain());  
  17.         mFloatContent.setAdapter(new MyAdapter(getSupportFragmentManager(), mFragments));  
  18.     }  
  19.   
  20.     class MyAdapter extends FragmentPagerAdapter {  
  21.   
  22.         private List<Fragment> mList;  
  23.   
  24.         public MyAdapter(FragmentManager fm, List<Fragment> list) {  
  25.             super(fm);  
  26.             mList = list;  
  27.         }  
  28.   
  29.         @Override  
  30.         public Fragment getItem(int arg0) {  
  31.             return mList.get(arg0);  
  32.         }  
  33.   
  34.         @Override  
  35.         public int getCount() {  
  36.             return mList.size();  
  37.         }  
  38.     }  
  39.   
  40. }  
xml

  1. <com.mingwei.floatlayout.FloatLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical" >  
  6.   
  7.     <RelativeLayout  
  8.         android:id="@+id/float_layout_top"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="200dip"  
  11.         android:background="@android:color/holo_blue_bright" >  
  12.   
  13.         <TextView  
  14.             android:layout_width="wrap_content"  
  15.             android:layout_height="wrap_content"  
  16.             android:layout_centerInParent="true"  
  17.             android:text="@string/float_top_layout_text" />  
  18.     </RelativeLayout>  
  19.   
  20.     <LinearLayout  
  21.         android:id="@+id/float_layout_float"  
  22.         android:layout_width="match_parent"  
  23.         android:layout_height="50dip"  
  24.         android:background="@android:color/holo_green_light"  
  25.         android:orientation="horizontal" >  
  26.   
  27.         <TextView  
  28.             android:layout_width="match_parent"  
  29.             android:layout_height="match_parent"  
  30.             android:layout_weight="1"  
  31.             android:gravity="center"  
  32.             android:text="@string/float_layout_test_listview" />  
  33.   
  34.         <TextView  
  35.             android:layout_width="match_parent"  
  36.             android:layout_height="match_parent"  
  37.             android:layout_weight="1"  
  38.             android:gravity="center"  
  39.             android:text="@string/float_layout_test_scrollview" />  
  40.     </LinearLayout>  
  41.   
  42.     <android.support.v4.view.ViewPager  
  43.         android:id="@id/float_layout_content"  
  44.         android:layout_width="match_parent"  
  45.         android:layout_height="match_parent"  
  46.         android:background="@android:color/holo_orange_light" >  
  47.     </android.support.v4.view.ViewPager>  
  48.   
  49. </com.mingwei.floatlayout.FloatLayout>  


为了保证事件拦截的时候找到View,并且这个View又不确定是ListView还是ScrollView,所以在ids中定义了一个固定的id用来标识View,从而能顺利的找到该View,此后的分发和拦截的时候都去用instanceof来区别view的类型而做不同的判断。
Github:https://github.com/Mingwei360/FloatLayout
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值