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

Android自定义控件——FloatLayout

2015-10-11 13:15 274 查看
本文介绍一个好多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
<?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
<?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();
}
}

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

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

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