您的位置:首页 > 移动开发 > Android开发

Android自定义控件——FloatLayout

2016-03-18 14:05 531 查看
本文介绍一个好多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

[java]
view plain
copy

/** 
 * 自定义的有悬浮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

[java]
view plain
copy

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

[html]
view plain
copy

<?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

[java]
view plain
copy

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

[html]
view plain
copy

<?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

[java]
view plain
copy

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

[html]
view plain
copy

<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>  

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

Github:https://github.com/Mingwei360/FloatLayout
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: