2源码的角度分析View
2017-09-04 本文已影响0人
帝乙岩
同为纵向滑动冲突(核心代码)
- 思路:根据业务逻辑处理,使用外部拦截
- 首先有个父容器StickyLayout继承LinearLayout,内部放header和一个Listview;
- 滑动规则:拦截:Header显示;listview滑动到顶部;Header隐藏,并且listview滑动手势为向下滑动;其他情况不拦截。
- 先看下布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.ceshiactivity.viewdemo.DemoCoreActivity">
<com.example.ceshiactivity.viewdemo.StickyLayout
android:id="@+id/sticky_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical">
<LinearLayout
android:id="@+id/sticky_header"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#78A524"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher" />
</LinearLayout>
<LinearLayout
android:id="@+id/sticky_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff4f7f9"
android:cacheColorHint="#00000000"
android:divider="#dddbdb"
android:dividerHeight="1.0px"
android:listSelector="@android:color/transparent" />
</LinearLayout>
</com.example.ceshiactivity.viewdemo.StickyLayout>
</RelativeLayout>
DemoCoreActivity
这里主要调用一个接口,以获得listview的item0位置
public class DemoCoreActivity extends AppCompatActivity implements StickyLayout.OnGiveUpTouchEventListener {
private StickyLayout stickyLayout;
ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo_core);
initView();
LinearLayout ll= (LinearLayout) findViewById(R.id.sticky_header);
stickyLayout= (StickyLayout) findViewById(R.id.sticky_layout);
stickyLayout.setOnGiveUpTouchEventListener(this);
}
private void initView() {
listView = (ListView)findViewById(R.id.list);
ArrayList<String> datas = new ArrayList<String>();
for (int i = 0; i < 50; i++) {
datas.add("name " + i);
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
R.layout.content_list_item, R.id.name, datas);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(DemoCoreActivity.this, "click item",
Toast.LENGTH_SHORT).show();
}
});
}
@Override
public boolean giveUpTouchEvent(MotionEvent event) {
//如果位置为0,获取0位置的view
if (listView.getFirstVisiblePosition() == 0) {
View view = listView.getChildAt(0);
//view在顶部位置单位像素
if (view != null && view.getTop() >= 0) {
return true;
}
}
return false;
}
然后是核心代码StickyLayout
public class StickyLayout extends LinearLayout {
private static final String TAG = "StickyLayout";
private static final boolean DEBUG = true;
//用来判断listview是否滑动到顶端
public interface OnGiveUpTouchEventListener {
public boolean giveUpTouchEvent(MotionEvent event);
}
private View mHeader;//上半部分布局
private View mContent;//下半部分布局
private OnGiveUpTouchEventListener mGiveUpTouchEventListener;
// header的高度 单位:px
private int mOriginalHeaderHeight;
private int mHeaderHeight;
//滑动方向
private int mStatus = STATUS_EXPANDED;
public static final int STATUS_EXPANDED = 1;
public static final int STATUS_COLLAPSED = 2;
//滚动的像素值
private int mTouchSlop;
// 分别记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0;
// 分别记录上次滑动的坐标(onInterceptTouchEvent)
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;
// 用来控制滑动角度,仅当角度a满足如下条件才进行滑动:tan a = deltaX / deltaY > 2
private static final int TAN = 2;
private boolean mIsSticky = true;
private boolean mInitDataSucceed = false;
private boolean mDisallowInterceptTouchEventOnHeader = true;
public StickyLayout(Context context) {
super(context);
}
public StickyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public StickyLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus && (mHeader == null || mContent == null)) {
initData();
}
}
private void initData() {
int headerId= getResources().getIdentifier("sticky_header", "id", getContext().getPackageName());
int contentId = getResources().getIdentifier("sticky_content", "id", getContext().getPackageName());
if (headerId != 0 && contentId != 0) {
mHeader = findViewById(headerId);
mContent = findViewById(contentId);
mOriginalHeaderHeight = mHeader.getMeasuredHeight();
mHeaderHeight = mOriginalHeaderHeight;
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
if (mHeaderHeight > 0) {
mInitDataSucceed = true;
}
if (DEBUG) {
Log.d(TAG, "mTouchSlop = " + mTouchSlop + "mHeaderHeight = " + mHeaderHeight);
}
} else {
throw new NoSuchElementException("Did your view with id \"sticky_header\" or \"sticky_content\" exists?");
}
}
/**
* 传递跟布局
* @param l this
*/
public void setOnGiveUpTouchEventListener(OnGiveUpTouchEventListener l) {
mGiveUpTouchEventListener = l;
}
/**
* 片段
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int intercepted = 0;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mLastXIntercept = x;
mLastYIntercept = y;
mLastX = x;
mLastY = y;
intercepted = 0;
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (mDisallowInterceptTouchEventOnHeader && y <= getHeaderHeight()) {
//竖直滑动距离小于header高度,不拦截
intercepted = 0;
} else if (Math.abs(deltaY) <= Math.abs(deltaX)) {
//竖直距离差小于等于水平距离不拦截
intercepted = 0;
} else if (mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop) {
//header展开状态,向上滑动,拦截
intercepted = 1;
} else if (mGiveUpTouchEventListener != null) {
//布局不为空
if (mGiveUpTouchEventListener.giveUpTouchEvent(event) && deltaY >= mTouchSlop) {
//listview滑动到顶部并向下滑动,拦截
intercepted = 1;
}
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = 0;
mLastXIntercept = mLastYIntercept = 0;
break;
}
default:
break;
}
if (DEBUG) {
Log.d(TAG, "intercepted=" + intercepted);
}
return intercepted != 0 && mIsSticky;
}
/**
* 片段
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!mIsSticky) {
return true;
}
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (DEBUG) {
Log.d(TAG, "mHeaderHeight=" + mHeaderHeight + " deltaY=" + deltaY + " mlastY=" + mLastY);
}
mHeaderHeight += deltaY;
setHeaderHeight(mHeaderHeight);
break;
}
case MotionEvent.ACTION_UP: {
// 这里做了下判断,当松开手的时候,会自动向两边滑动,具体向哪边滑,要看当前所处的位置
int destHeight = 0;
if (mHeaderHeight <= mOriginalHeaderHeight * 0.5) {
destHeight = 0;
mStatus = STATUS_COLLAPSED;
} else {
destHeight = mOriginalHeaderHeight;
mStatus = STATUS_EXPANDED;
}
// 慢慢滑向终点
this.smoothSetHeaderHeight(mHeaderHeight, destHeight, 500);
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return true;
}
public void smoothSetHeaderHeight(final int from, final int to, long duration) {
smoothSetHeaderHeight(from, to, duration, false);
}
/**
* 线程设置高度变化
* @param from head初始高度
* @param to 移动到的高度
* @param duration 时间
* @param modifyOriginalHeaderHeight
*/
public void smoothSetHeaderHeight(final int from, final int to, long duration, final boolean modifyOriginalHeaderHeight) {
final int frameCount = (int) (duration / 1000f * 30) + 1;
//速度公式
final float partation = (to - from) / (float) frameCount;
new Thread("Thread#smoothSetHeaderHeight") {
@Override
public void run() {
for (int i = 0; i < frameCount; i++) {
//动态变化
final int height;
if (i == frameCount - 1) {
height = to;
} else {
height = (int) (from + partation * i);
}
post(new Runnable() {
public void run() {
setHeaderHeight(height);
}
});
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (modifyOriginalHeaderHeight) {
setOriginalHeaderHeight(to);
}
};
}.start();
}
public void setOriginalHeaderHeight(int originalHeaderHeight) {
mOriginalHeaderHeight = originalHeaderHeight;
}
public void setHeaderHeight(int height, boolean modifyOriginalHeaderHeight) {
if (modifyOriginalHeaderHeight) {
setOriginalHeaderHeight(height);
}
setHeaderHeight(height);
}
/**
* 赋值 并设置高度到Header中
* @param height
*/
public void setHeaderHeight(int height) {
if (!mInitDataSucceed) {
initData();
}
if (DEBUG) {
Log.d(TAG, "setHeaderHeight height=" + height);
}
if (height <= 0) {
height = 0;
} else if (height > mOriginalHeaderHeight) {
//这行注释掉可实现下拉到任意位置后弹回
height = mOriginalHeaderHeight;
}
if (height == 0) {
mStatus = STATUS_COLLAPSED;
} else {
mStatus = STATUS_EXPANDED;
}
if (mHeader != null && mHeader.getLayoutParams() != null) {
mHeader.getLayoutParams().height = height;
mHeader.requestLayout();
mHeaderHeight = height;
} else {
if (DEBUG) {
Log.e(TAG, "null LayoutParams when setHeaderHeight");
}
}
}
public int getHeaderHeight() {
return mHeaderHeight;
}
public void setSticky(boolean isSticky) {
mIsSticky = isSticky;
}
public void requestDisallowInterceptTouchEventOnHeader(boolean disallowIntercept) {
mDisallowInterceptTouchEventOnHeader = disallowIntercept;
}
}
效果图:
纵向与纵向滑动冲突.jpg遗留问题手指向上滑动不离开屏幕则listview无法滑动(待解决)