自定義Stickheaderlayout介紹
效果

Stickheaderlayout
原理
Stickheaderlayout 繼承 ViewGroup ,總體分為三塊
- 頭部
可以放置上下不可滾動(dòng)的view - 標(biāo)題
可以放置上下不可滾動(dòng)的view(不想出現(xiàn)標(biāo)題則可以僅放一條橫線) - 內(nèi)容
可以放置GridView、ListView、RecyclerView、ScrollView、ViewPager并且兼容下拉刷新跟上拉自動(dòng)加載更多
使用ViewDragHelper跟View事件的攔截相關(guān)的東西,ViewDragHelper不了解的可訪問http://www.itdecent.cn/p/bfe857e509c9
IpmlScrollChangListener 介紹
包含四個(gè)方法
- boolean isReadyForPull()
是否可以將head拉下來 - void onStartScroll()
滾動(dòng)開始 - void onStopScroll()
滾動(dòng)開始 - void onScrollChange(int dy, int totallDy)
滾動(dòng)距離的變化 dy:滾動(dòng)的距離 totallDy:可滾動(dòng)的最大距離 配合 setRetentionHeight(int )可以用于做漸變的titlebar (見viewPagerDemo效果圖)
StickHeaderLayout 方法介紹
- setRetentionHeight(int retentionHeight)
設(shè)置向上滾動(dòng)headView 保留的高度 - setScroll(IpmlScrollChangListener scroll)
設(shè)置滾動(dòng)回調(diào)
ViewDragHelper初始化
- 滾動(dòng)狀態(tài)監(jiān)聽
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
if (scroll != null) {
if (state == ViewDragHelper.STATE_IDLE) {
scroll.onStopScroll();
}
if (state == ViewDragHelper.STATE_SETTLING) {
scroll.onStartScroll();
}
}
}
- 判斷ViewDragHelper是否觸發(fā)滾動(dòng) 頭部、標(biāo)題、內(nèi)容都可以滑動(dòng)
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
- 手指按住contentView時(shí)滾動(dòng)時(shí),頭部、標(biāo)題、內(nèi)容的位置變化,返回contentView是移動(dòng)距離
public int clampViewPositionVertical(View child, int top, int dy) {
if (child == headView) {
int headViewTop = headView.getTop() + dy;
if (headViewTop > 0) {
headViewTop = 0;
} else if (headViewTop < -headHeight + retentionHeight) {
headViewTop = -headHeight + retentionHeight;
}
return headViewTop;
}
if (child == titleView) {
int titleTop = titleView.getTop() + dy;
if (titleTop > headHeight) {
titleTop = headHeight;
} else if (titleTop < retentionHeight) {
titleTop = retentionHeight;
}
return titleTop;
}
int contentTop = contentView.getTop() + dy;
if (contentTop > headHeight + titleHeight) {
contentTop = headHeight + titleHeight;
} else if (contentTop < titleHeight + retentionHeight) {
contentTop = titleHeight + retentionHeight;
}
return contentTop;
}
- 手指離開屏幕時(shí),觸發(fā)快速滾動(dòng)
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (dragEdge == DragEdge.Bottom) {
if (yvel < -mDragHelper.getMinVelocity()) {
mDragHelper.smoothSlideViewTo(contentView, 0, titleHeight + retentionHeight);
} else if (releasedChild == headView) {
mDragHelper.smoothSlideViewTo(headView, 0, -headHeight + retentionHeight);
} else {
mDragHelper.smoothSlideViewTo(titleView, 0, retentionHeight);
}
} else if (dragEdge == DragEdge.Top) {
if (releasedChild == contentView) {
mDragHelper.smoothSlideViewTo(contentView, 0, titleHeight + headHeight);
} else if (releasedChild == headView) {
mDragHelper.smoothSlideViewTo(headView, 0, 0);
} else {
mDragHelper.smoothSlideViewTo(titleView, 0, headHeight);
}
}
invalidate();
}
- 觸發(fā)快速滾動(dòng)后頭部、標(biāo)題、內(nèi)容的位置變化
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
if (changedView == contentView) {
int contentTop = contentView.getTop();
titleView.layout(0, contentTop - titleHeight, titleView.getMeasuredWidth(), contentTop);
headView.layout(0, contentTop - titleHeight - headHeight, titleView.getMeasuredWidth(), contentTop - titleHeight);
} else if (changedView == headView) {
int contentTop = headView.getTop();
titleView.layout(0, contentTop + headHeight, titleView.getMeasuredWidth(), contentTop + headHeight + titleHeight);
contentView.layout(0, contentTop + headHeight + titleHeight, contentView.getMeasuredWidth(), contentTop + headHeight + titleHeight + contentHeight);
} else {
int contentTop = titleView.getTop();
headView.layout(0, contentTop - headHeight, headView.getMeasuredWidth(), contentTop);
contentView.layout(0, contentTop + titleHeight, contentView.getMeasuredWidth(), contentTop + titleHeight + contentHeight);
}
if (scroll != null) {
scroll.onScrollChange(Math.abs(headView.getTop()), headHeight - retentionHeight);
}
}
手勢的相關(guān)處理
- 事件的攔截
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
switch (action) {
case MotionEvent.ACTION_DOWN:
mDragHelper.processTouchEvent(ev);
sX = ev.getRawX();
sY = ev.getRawY();
mIsBeingDragged = false;
break;
case MotionEvent.ACTION_MOVE:
checkCanDrag(ev);
if (dragEdge == DragEdge.Left || dragEdge == DragEdge.Right || dragEdge == DragEdge.None) {
return false;
}
if (scroll != null && !scroll.isReadyForPull()) {
if (dragEdge == DragEdge.Bottom && titleView.getTop() == headHeight) {
return true;
}
return false;
} else {
if (dragEdge == DragEdge.Bottom) {
if (titleView.getTop() == retentionHeight) {
mIsBeingDragged = false;
dragEdge = DragEdge.None;
}
} else if (dragEdge == DragEdge.Top) {
if (titleView.getTop() == headHeight) {
dragEdge = DragEdge.None;
mIsBeingDragged = false;
}
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsBeingDragged = false;
mDragHelper.processTouchEvent(ev);
break;
default:
mDragHelper.processTouchEvent(ev);
break;
}
return mIsBeingDragged;
}
- 判斷手勢的滑動(dòng)方向
public enum DragEdge {
None,
Left,
Top,
Right,
Bottom
}
private void checkCanDrag(MotionEvent ev) {
float dx = ev.getRawX() - sX;
float dy = ev.getRawY() - sY;
float angle = Math.abs(dy / dx);
angle = (float) Math.toDegrees(Math.atan(angle));
if (angle < 45) {
if (dx > 0) {
dragEdge = DragEdge.Left;
} else if (dx < 0) {
dragEdge = DragEdge.Right;
}
} else {
if (dy > 0) {
dragEdge = DragEdge.Top;
} else if (dy < 0) {
dragEdge = DragEdge.Bottom;
}
}
mIsBeingDragged = true;
}
- 事件的處理
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
mDragHelper.processTouchEvent(event);
case MotionEvent.ACTION_MOVE: {
checkCanDrag(event);
if (mIsBeingDragged) {
getParent().requestDisallowInterceptTouchEvent(true);
try {
mDragHelper.processTouchEvent(event);
} catch (Exception e) {
}
}
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsBeingDragged = false;
mDragHelper.processTouchEvent(event);
break;
default://handle other action, such as ACTION_POINTER_DOWN/UP
mDragHelper.processTouchEvent(event);
}
return super.onTouchEvent(event) || mIsBeingDragged || action == MotionEvent.ACTION_DOWN;
}
使用 以ListView 為例子
- 布局文件
<?xml version="1.0" encoding="utf-8"?>
<com.sys.blackcat.stickheaderlayout.StickHeaderLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:id="@+id/stick_header_layout"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@color/red"
android:gravity="center"
android:text="我是頭部"
android:textColor="@color/white" />
<TextView
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@color/blue"
android:gravity="center"
android:text="我是標(biāo)題"
android:textColor="@color/white" />
<!--內(nèi)容-->
<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/green" />
</com.sys.blackcat.stickheaderlayout.StickHeaderLayout>
- Activity 邏輯處理
public class ListViewDemo extends AppCompatActivity {
private StickHeaderLayout layout;
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_demo);
setTitle(R.string.list_example);
layout = (StickHeaderLayout) findViewById(R.id.stick_header_layout);
listView = (ListView) findViewById(R.id.list);
layout.setScroll(new IpmlScrollChangListener() {
@Override
public boolean isReadyForPull() {
return DemoUtils.isOnTop(listView);
}
@Override
public void onStartScroll() {
}
@Override
public void onStopScroll() {
}
@Override
public void onScrollChange(int dy, int totallDy) {
}
});
listView.addHeaderView(getLayoutInflater().inflate(R.layout.list_head, null));
listView.setAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, DemoUtils.getData()));
}
}
- DemoUtils 判斷內(nèi)容部分是否可以滑動(dòng)
public static boolean isOnTop(ViewGroup viewGroup) {
int[] groupLocation = new int[2];
viewGroup.getLocationOnScreen(groupLocation);
int[] itemLocation = new int[2];
if (viewGroup.getChildAt(0) != null) {
viewGroup.getChildAt(0).getLocationOnScreen(itemLocation);
return groupLocation[1] == itemLocation[1];
}
return false;
}
了解更多請(qǐng)?jiān)L問
https://github.com/yang747046912/Stickheaderlayout/