参考了:https://www.jianshu.com/p/1267c9506d3b
已测试通过手机:小米,三星,华为
然后发现各种手机不兼容,而且有个问题:当上面布局高度不足的时候,会
直接导致不能滑动。
于是开始了极致的思考,发现了mViewHeight高度不准,于是用了getViewTreeObserver().addOnGlobalLayoutListener()这种方式获取高度,这里不得不吐槽,即便在onMeasure()里面,高度getHeight(),getMeasureHeight()都不准,最新版本如下:
public class ScrollViewContainer extends RelativeLayout
public static final int AUTO_UP = 0;
public static final int AUTO_DOWN = 1;
public static final int DONE = 2;
public static final float SPEED = 6.5f;
private boolean isMeasured = false;
private VelocityTracker vt;
private int mViewHeight;
private int mViewWidth;
private View topView;
private View bottomView;
private boolean canPullDown;
private boolean canPullUp;
private int state = DONE;
private int mCurrentViewIndex = 0;
private float mMoveLen;
private MyTimer mTimer;
private float mLastY;
private int mEvents;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (mMoveLen != 0) {
if (state == AUTO_UP) {
mMoveLen -= SPEED;
if (mMoveLen <= -mViewHeight) {
mMoveLen = -mViewHeight;
state = DONE;
mCurrentViewIndex = 1;
}
} else if (state == AUTO_DOWN) {
mMoveLen += SPEED;
if (mMoveLen >= 0) {
mMoveLen = 0;
state = DONE;
mCurrentViewIndex = 0;
}
} else {
mTimer.cancel();
}
}
requestLayout();
}
};
public ScrollViewContainer(Context context) {
super(context);
init();
}
public ScrollViewContainer(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ScrollViewContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mTimer = new MyTimer(handler);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
if (vt == null)
vt = VelocityTracker.obtain();
else
vt.clear();
mLastY = ev.getY();
vt.addMovement(ev);
mEvents = 0;
break;
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_POINTER_UP:
mEvents = -1;
break;
case MotionEvent.ACTION_MOVE:
vt.addMovement(ev);
if (canPullUp && mCurrentViewIndex == 0 && mEvents == 0) {
mMoveLen += (ev.getY() - mLastY);
if (mMoveLen > 0) {
mMoveLen = 0;
mCurrentViewIndex = 0;
} else if (mMoveLen < -mViewHeight) {
mMoveLen = -mViewHeight;
mCurrentViewIndex = 1;
}
if (mMoveLen < -8) {
ev.setAction(MotionEvent.ACTION_CANCEL);
}
} else if (canPullDown && mCurrentViewIndex == 1 && mEvents == 0) {
mMoveLen += (ev.getY() - mLastY);
if (mMoveLen < -mViewHeight) {
mMoveLen = -mViewHeight;
mCurrentViewIndex = 1;
} else if (mMoveLen > 0) {
mMoveLen = 0;
mCurrentViewIndex = 0;
}
if (mMoveLen > 8 - mViewHeight) {
// ��ֹ�¼���ͻ
ev.setAction(MotionEvent.ACTION_CANCEL);
}
} else
mEvents++;
mLastY = ev.getY();
requestLayout();
break;
case MotionEvent.ACTION_UP:
mLastY = ev.getY();
vt.addMovement(ev);
vt.computeCurrentVelocity(700);
float mYV = vt.getYVelocity();
if (mMoveLen == 0 || mMoveLen == -mViewHeight)
break;
if (Math.abs(mYV) < 500) {
//
if (mMoveLen <= -mViewHeight / 2) {
state = AUTO_UP;
} else if (mMoveLen > -mViewHeight / 2) {
state = AUTO_DOWN;
}
} else {
if (mYV < 0)
state = AUTO_UP;
else
state = AUTO_DOWN;
}
mTimer.schedule(2);
try {
vt.recycle();
vt = null;
} catch (Exception e) {
e.printStackTrace();
}
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
topView.layout(0, (int) mMoveLen, mViewWidth,
topView.getMeasuredHeight() + (int) mMoveLen);
bottomView.layout(0, topView.getMeasuredHeight() + (int) mMoveLen,
mViewWidth, topView.getMeasuredHeight() + (int) mMoveLen
+ bottomView.getMeasuredHeight());
}
private int getStatusHeight() {
/**
* 获取状态栏高度——方法1
* */
int statusBarHeight1 = -1;
//获取status_bar_height资源的ID
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
//根据资源ID获取响应的尺寸值
statusBarHeight1 = getResources().getDimensionPixelSize(resourceId);
}
return statusBarHeight1;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!isMeasured) {
isMeasured = true;
mViewWidth = getMeasuredWidth();
topView = getChildAt(0);
bottomView = getChildAt(1);
getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mViewHeight = getHeight();
ViewGroup topScroll = (ViewGroup) topView;
ViewGroup topContainer = (ViewGroup) topScroll.getChildAt(0);
topContainer.setMinimumHeight(mViewHeight);
topScroll.setMinimumHeight(mViewHeight);
bottomView.setOnTouchListener(bottomViewTouchListener);
topView.setOnTouchListener(topViewTouchListener);
getViewTreeObserver()
.removeGlobalOnLayoutListener(this);
}
});
}
}
private OnTouchListener topViewTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
ScrollView sv = (ScrollView) topView;
int scrollY = sv.getScrollY();
int containerHeight = sv.getChildAt(0).getMeasuredHeight();
int scrollHeight = sv.getMeasuredHeight();
if (containerHeight < scrollHeight) {//留头
//三星的问题,当高度不足时,topView的高度和container高度不等
sv.getChildAt(0).setMinimumHeight(scrollHeight);
containerHeight = scrollHeight;
mViewHeight = scrollHeight;
}
if ((scrollY == containerHeight - scrollHeight) && mCurrentViewIndex == 0)
canPullUp = true;
else
canPullUp = false;
return false;
}
};
private OnTouchListener bottomViewTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
ScrollView sv = (ScrollView) v;
int containerHeight = sv.getChildAt(0).getMeasuredHeight();
int scrollHeight = sv.getMeasuredHeight();
if (containerHeight < scrollHeight) {
//三星的问题,当高度不足时,topView的高度和container高度不等
sv.getChildAt(0).setMinimumHeight(scrollHeight);
mViewHeight = scrollHeight;
}
if (sv.getScrollY() == 0 && mCurrentViewIndex == 1)
canPullDown = true;
else
canPullDown = false;
return false;
}
};
class MyTimer {
private Handler handler;
private Timer timer;
private MyTask mTask;
public MyTimer(Handler handler) {
this.handler = handler;
timer = new Timer();
}
public void schedule(long period) {
if (mTask != null) {
mTask.cancel();
mTask = null;
}
mTask = new MyTask(handler);
timer.schedule(mTask, 0, period);
}
public void cancel() {
if (mTask != null) {
mTask.cancel();
mTask = null;
}
}
class MyTask extends TimerTask {
private Handler handler;
public MyTask(Handler handler) {
this.handler = handler;
}
@Override
public void run() {
handler.obtainMessage().sendToTarget();
}
}
}
}
其实主要就是onMeasure那里改了,而且将上面的最小高度设为当前控件高度。这样当高度不足时,可以自动调整,还有就是三星手机上划的时候会算的和onMeasure不一致,所以滑动的时候重新计算一下高度,保证三星手机可以滑动,为了尽量兼容,我这里尽力没有通过厂商识别,现在效果还可以。当然这个有缺陷就是下面那个scrollview不能里面嵌套recyclerview,于是干脆直接加子布局在该布局中追加内容了,反正图文详情就是图片,写起来不算复杂,这里也贴一下:
LinearLayout linear_pictures = (LinearLayout) findViewById(R.id.linear_pictures);
// loadData();
linear_pictures.removeAllViews();
for (int i = 0; i < descriptionUrls.size(); i++) {
ImageView image = new ImageView(this);
if (urls!= null && urls.size() > 0)
linear_pictures.addView(image);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) image.getLayoutParams();
params.width = MATCH_PARENT;
params.height = WRAP_CONTENT;
image.setLayoutParams(params);
image.setAdjustViewBounds(true);
Glide.with(this)
.load(urls.get(i))
.signature(new OriginalKey(descriptionUrls.get(i), EmptySignature.obtain()))
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.placeholder(R.drawable.img_big_place)
.into(image);
布局这里不方便贴(公司项目),贴个大概意思,大家明白用法就行了。
<ScrollViewContainer>
<ScrollView>
<LinearLayout>
</LinearLayout>
</ScrollView>
<ScrollView>
<LinearLayout>
</LinearLayout>
</ScrollView>
</ScrollViewContainer>
后续问题,图文详情上面的控件里面有viewpager轮播图
这里遇到一个分发机制问题,就是viewpager首次滑动的时候,在内部旋转画圆,外面图文详情不会滚动,但是如果点到轮播图外面再在轮播图内部旋转画圆,会带动图文详情部分进行滚动。
经过不断调试,发现是因为这个滚动的自定义控件里面在事件分发的时候就把事件处理了,所以会影响上面viewpager的使用。
简单需求:
轮播图上进行操作时,不要影响外面的scrollview等发生滚动。于是做了如下修改,当当前控件的父控件为viewpager时(只有一层,不判断那么复杂,viewpager内如果多层就要递归),则直接返回父类的事件分发(因为是继承relativelayout,所以不用对滚动进行处理,否则需在图文详情自定义控件中加入:requestDisallowInterceptTouchEvent(true);),此时要注意一点特殊情况:当viewpager滑动到外部时,会触发这里dispatchTouchEvent的移动和按下事件,需要返回不进行处理。处理后如下(轮播图部分不用修改)。
package com.yonxin.mall.view.slidinguppanel;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
/**
* ��������ScrollView������
* ������������http://dwtedx.com
*
* @author
public class ScrollViewContainer extends RelativeLayout
/**
* �Զ��ϻ�
*/
public static final int AUTO_UP = 0;
/**
* �Զ��»�
*/
public static final int AUTO_DOWN = 1;
/**
* �������
*/
public static final int DONE = 2;
/**
* �����ٶ�
*/
public static final float SPEED = 6.5f;
private boolean isMeasured = false;
/**
* ���ڼ����ֻ������ٶ�
*/
private VelocityTracker vt;
private int mViewHeight;
private int mViewWidth;
private View topView;
private View bottomView;
private boolean canPullDown;
private boolean canPullUp;
private int state = DONE;
/**
* ��¼��ǰչʾ�����ĸ�view��0��topView��1��bottomView
*/
private int mCurrentViewIndex = 0;
/**
* �ֻ������룬����ǿ��Ʋ��ֵ���Ҫ����
*/
private float mMoveLen;
private MyTimer mTimer;
private float mLastY;
/**
* ���ڿ����Ƿ�䶯���ֵ���һ��������mEvents==0ʱ���ֿ�����ק�ˣ�mEvents==-1ʱ����������Ҫ�����ĵ�һ��move�¼���
* �����ȥ������϶����Ĺؼ�
*/
private int mEvents;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (mMoveLen != 0) {
if (state == AUTO_UP) {
mMoveLen -= SPEED;
if (mMoveLen <= -mViewHeight) {
mMoveLen = -mViewHeight;
state = DONE;
mCurrentViewIndex = 1;
}
} else if (state == AUTO_DOWN) {
mMoveLen += SPEED;
if (mMoveLen >= 0) {
mMoveLen = 0;
state = DONE;
mCurrentViewIndex = 0;
}
} else {
mTimer.cancel();
}
}
requestLayout();
}
};
public ScrollViewContainer(Context context) {
super(context);
init();
}
public ScrollViewContainer(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ScrollViewContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mTimer = new MyTimer(handler);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
View v = getViewAtViewGroup((int) (ev.getRawX()), (int) (ev.getRawY()));
if(v !=null && v.getParent()!=null && v.getParent() instanceof ViewPager){
return super.dispatchTouchEvent(ev);
}
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
if (vt == null)
vt = VelocityTracker.obtain();
else
vt.clear();
mLastY = ev.getY();
vt.addMovement(ev);
mEvents = 0;
break;
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_POINTER_UP:
mEvents = -1;
break;
case MotionEvent.ACTION_MOVE:
if(vt == null)
break;
vt.addMovement(ev);
if (canPullUp && mCurrentViewIndex == 0 && mEvents == 0) {
mMoveLen += (ev.getY() - mLastY);
// ��ֹ����Խ��
if (mMoveLen > 0) {
mMoveLen = 0;
mCurrentViewIndex = 0;
} else if (mMoveLen < -mViewHeight) {
mMoveLen = -mViewHeight;
mCurrentViewIndex = 1;
}
if (mMoveLen < -8) {
// ��ֹ�¼���ͻ
ev.setAction(MotionEvent.ACTION_CANCEL);
}
} else if (canPullDown && mCurrentViewIndex == 1 && mEvents == 0) {
mMoveLen += (ev.getY() - mLastY);
// ��ֹ����Խ��
if (mMoveLen < -mViewHeight) {
mMoveLen = -mViewHeight;
mCurrentViewIndex = 1;
} else if (mMoveLen > 0) {
mMoveLen = 0;
mCurrentViewIndex = 0;
}
if (mMoveLen > 8 - mViewHeight) {
// ��ֹ�¼���ͻ
ev.setAction(MotionEvent.ACTION_CANCEL);
}
} else
mEvents++;
mLastY = ev.getY();
requestLayout();
break;
case MotionEvent.ACTION_UP:
if(vt == null)
break;
mLastY = ev.getY();
vt.addMovement(ev);
vt.computeCurrentVelocity(700);
// ��ȡY������ٶ�
float mYV = vt.getYVelocity();
if (mMoveLen == 0 || mMoveLen == -mViewHeight)
break;
if (Math.abs(mYV) < 500) {
// �ٶ�С��һ��ֵ��ʱ������ֹ�ͷţ���ʱ������View�����ƶ�ȡ���ڻ����ľ���
if (mMoveLen <= -mViewHeight / 2) {
state = AUTO_UP;
} else if (mMoveLen > -mViewHeight / 2) {
state = AUTO_DOWN;
}
} else {
// ̧����ָʱ�ٶȷ����������View�����ƶ�
if (mYV < 0)
state = AUTO_UP;
else
state = AUTO_DOWN;
}
mTimer.schedule(2);
try {
vt.recycle();
vt = null;
} catch (Exception e) {
e.printStackTrace();
}
break;
}
return super.dispatchTouchEvent(ev);
}
public View getViewAtViewGroup(int x, int y) {
return findViewByXY(this, x, y);
}
private View getTouchTarget(View view, int x, int y) {
View targetView = null;
// 判断view是否可以聚焦
ArrayList<View> TouchableViews = view.getTouchables();
for (View child : TouchableViews) {
if (isTouchPointInView(child, x, y)) {
targetView = child;
break;
}
}
return targetView;
}
private boolean isTouchPointInView(View view, int x, int y) {
int[] location = new int[2];
view.getLocationOnScreen(location);
int left = location[0];
int top = location[1];
int right = left + view.getMeasuredWidth();
int bottom = top + view.getMeasuredHeight();
if (view.isClickable() && y >= top && y <= bottom && x >= left
&& x <= right) {
return true;
}
return false;
}
private View findViewByXY(View view, int x, int y) {
View targetView = getTouchTarget(view,x,y);
if(targetView == null)
return null;
else if(targetView instanceof ViewGroup){
ViewGroup v = (ViewGroup) targetView;
for(int i = 0; i < v.getChildCount(); i++){
View tempView = findViewByXY(v.getChildAt(i),x,y);
if(tempView!=null){
return tempView;
}
}
}
return targetView;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
topView.layout(0, (int) mMoveLen, mViewWidth,
topView.getMeasuredHeight() + (int) mMoveLen);
bottomView.layout(0, topView.getMeasuredHeight() + (int) mMoveLen,
mViewWidth, topView.getMeasuredHeight() + (int) mMoveLen
+ bottomView.getMeasuredHeight());
}
private int getStatusHeight() {
/**
* 获取状态栏高度——方法1
* */
int statusBarHeight1 = -1;
//获取status_bar_height资源的ID
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
//根据资源ID获取响应的尺寸值
statusBarHeight1 = getResources().getDimensionPixelSize(resourceId);
}
return statusBarHeight1;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!isMeasured) {
isMeasured = true;
mViewWidth = getMeasuredWidth();
topView = getChildAt(0);
bottomView = getChildAt(1);
getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mViewHeight = getHeight();
ViewGroup topScroll = (ViewGroup) topView;
ViewGroup topContainer = (ViewGroup) topScroll.getChildAt(0);
topContainer.setMinimumHeight(mViewHeight);
topScroll.setMinimumHeight(mViewHeight);
bottomView.setOnTouchListener(bottomViewTouchListener);
topView.setOnTouchListener(topViewTouchListener);
getViewTreeObserver()
.removeGlobalOnLayoutListener(this);
}
});
}
}
private OnTouchListener topViewTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
ScrollView sv = (ScrollView) topView;
int scrollY = sv.getScrollY();
int containerHeight = sv.getChildAt(0).getMeasuredHeight();
int scrollHeight = sv.getMeasuredHeight();
if (containerHeight < scrollHeight) {//留头
//三星的问题,当高度不足时,topView的高度和container高度不等
sv.getChildAt(0).setMinimumHeight(scrollHeight);
containerHeight = scrollHeight;
mViewHeight = scrollHeight;
}
if ((scrollY == containerHeight - scrollHeight) && mCurrentViewIndex == 0)
canPullUp = true;
else
canPullUp = false;
return false;
}
};
private OnTouchListener bottomViewTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
ScrollView sv = (ScrollView) v;
int containerHeight = sv.getChildAt(0).getMeasuredHeight();
int scrollHeight = sv.getMeasuredHeight();
if (containerHeight < scrollHeight) {
//三星的问题,当高度不足时,topView的高度和container高度不等
sv.getChildAt(0).setMinimumHeight(scrollHeight);
mViewHeight = scrollHeight;
}
if (sv.getScrollY() == 0 && mCurrentViewIndex == 1)
canPullDown = true;
else
canPullDown = false;
return false;
}
};
class MyTimer {
private Handler handler;
private Timer timer;
private MyTask mTask;
public MyTimer(Handler handler) {
this.handler = handler;
timer = new Timer();
}
public void schedule(long period) {
if (mTask != null) {
mTask.cancel();
mTask = null;
}
mTask = new MyTask(handler);
timer.schedule(mTask, 0, period);
}
public void cancel() {
if (mTask != null) {
mTask.cancel();
mTask = null;
}
}
class MyTask extends TimerTask {
private Handler handler;
public MyTask(Handler handler) {
this.handler = handler;
}
@Override
public void run() {
handler.obtainMessage().sendToTarget();
}
}
}
}