后面持续as
问题
(1)解决ScrollView加载完数据后会滑到下面
用ScrollView嵌套Webview或者listview之,当加载数据完之后,scrollview会滑动一段距离这个问题。最开始的时候用view.postdelay,让数据加载完之后scrollview.scrollto(0,0);
这样去处理,但是也出现问题,delay延迟的时间比较短的话,好像没效果还是会滑动一段距离,delay时间长的话会出现界面下面闪下在变到最上面,体验不好。
经研究发现是因为焦点问题,scrollview会滑动到焦点所在的子view中,解决方法也简单,在ScrollView嵌套的第一个Linearlayout中加入如下2行代码就好:
android:focusable="true"
android:focusableInTouchMode="true"
一、 Android 控制ScrollView滚动到底部,顶部,指定位置
1.1
public static void scrollToBottom(final View scroll, final View inner) {
Handler mHandler = new Handler();
mHandler.post(new Runnable() {
public void run() {
if (scroll == null || inner == null) {
return;
}
int offset = inner.getMeasuredHeight() - scroll.getHeight();
if (offset < 0) {
offset = 0;
}
scroll.scrollTo(0, offset);
}
});
}
使用fullScrol()
下面我们看一下这个函数:
scrollView.fullScroll(ScrollView.FOCUS_DOWN);滚动到底部
scrollView.fullScroll(ScrollView.FOCUS_UP);滚动到顶部
需要注意的是,该方法不能直接被调用
因为Android很多函数都是基于消息队列来同步,所以需要一部操作,
addView完之后,不等于马上就会显示,而是在队列中等待处理,虽然很快,但是如果立即调用fullScroll, view可能还没有显示出来,所以会失败
应该通过handler在新线程中更新
handler.post(new Runnable() {
@Override
public void run() {
scrollView.fullScroll(ScrollView.FOCUS_DOWN);
}
});
1.2 滑动到指定位置
handler.post(new Runnable() {
@Override
public void run() {
//To change body of implemented methods use File | Settings | File Templates.
// mRootScrollView.fullScroll(ScrollView.FOCUS_DOWN);
int[] location = new int[2];
view.getLocationOnScreen(location); --- 获取位置
int offset = location[1] - mRootScrollView.getMeasuredHeight();
if (offset < 0) {
offset = 0;
}
mRootScrollView.smoothScrollTo(0, offset);
}
});
第一,handler.post(runnable);并不是新开线程,只是让UI主线程去并发执行run()方法。
第二,之所以放在handler里,是为了保证View都已经绘制完成。不然,你放在resume()中执行,应该也可以的。
第三,smoothScrollTo类似于scrollTo,但是滚动的时候是平缓的而不是立即滚动到某处。另外,smoothScrollTo()方法可以打断滑动动画。
1.3
ScrollView滚动到指定位置 (平滑 慢速 动画)-- 添加动画
private Runnable runnable = new Runnable() {
@Override
public void run() {
scrollToPosition(0,600);
}
};
public void scrollToPosition(int x,int y) {
ObjectAnimator xTranslate = ObjectAnimator.ofInt(ScrollContainer, "scrollX", x);
ObjectAnimator yTranslate = ObjectAnimator.ofInt(ScrollContainer, "scrollY", y);
AnimatorSet animators = new AnimatorSet();
animators.setDuration(1000L);
animators.playTogether(xTranslate, yTranslate);
animators.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator arg0) {
// TODO Auto-generated method stub
}
@Override
public void onAnimationRepeat(Animator arg0) {
// TODO Auto-generated method stub
}
@Override
public void onAnimationEnd(Animator arg0) {
// TODO Auto-generated method stub
}
@Override
public void onAnimationCancel(Animator arg0) {
// TODO Auto-generated method stub
}
});
animators.start();
}
二、 ScrollView判断是否滑动到底部和顶部
1. getMeasuredHeight()是实际View的大小,与屏幕无关,
getMeasureHeight()是View的实际高度,也就是说不管view是否可见,是否部分可见,我们得到的始终是一个固定值。
2. getHeight的大小此时则是屏幕的大小
而getHeight()是按照用户的视角来说的,也就是“用户看到多少,getHeight()返回值就是多少”,当View超出屏幕范围的时候,
此时得到的就是View的实际高度-超出屏幕范围的高度
3. scrollview 子 view 的高度
View childView = getChildAt(0 //i);
childView.getMeasuredHeight() 表示得到子View的实际高度,
4.关于滚动:scrollTo、scrollBy、getScrollX、getScrollY这几个方法的区别:
ScrollTo()表示移动视图到那个坐标点,就是说那个视图调用这个方法,
那么这个视图的(x,y)点就和此视图原始位置的(0,0)点对其,二View的坐标系统是左上角为(0,0),向右为正,向下为正,因此ScrollTo(0,30)
就是要把此视图的(0,30)点移动到原始位置(0,0)点,具体向上还是向下完全看当前的视图的位置来决定吗(关键认知:所有的滑动都是移动视图的)
ScrollBy()表示在视图的X、Y方向上各移动dx,dy距离,同样向上为正,
向左为正,例如:ScrollBy(0,30)表示将此视图向上移动30dpi,为什么是这样呢?因为view的位置是相对于其父容器来说的,假设刚开始View的左上角和父容器重合View高度和宽度为50dpi,
那么View的位置是(0,0,50,50),然后向上移动30dpi,此时View的位置是(0,-30,50,20),那么原视图的位置减去移动后的位置,结果就是正的30dpi(源码中ScrollBy其实是对ScrollTo的再封装)
在源码中有两个变量:MScrollX,MScrollY,这两个变量是专门用于记录位
置的变量,也就是当前View的位置,ScrollTo()源码如下: “`
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX; //将当前位置设置为老的位置
int oldY = mScrollY;
mScrollX = x; //将需要滚动到的位置设置为当前位置
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
//ScrollBy也是和MScrollX和MScrollY相关的
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
而getScrollX()和getScrollY()方法源码中就是直接返回MScrollX和
MScrollY的值,在观察上面的源码,你会发现MScrollX的值是等于ScrollTo(中设置的值的),因此:getScrollY =
mScrollY = ScrollTo(0,Y)
总结:假如现在view在初始位置,然后我们向上滑动50dpi,就是让view的(0,50)和原视图的(0,0)重合,此时相当于调用ScrollTo(0,50),
此时的MScrollY等于50,因此getScrollY()的返回值就是50
5. ScrollView只能放一个子View,当ScrollView滑动到最底部时,此时ScrollView内容的高度就是:超出屏幕的高度+此控件的高度,
childView.getMeasureHeight() == getScrollY + chilView.getHeight()
而当不是ScrollView是lIstView这样可以有多个子View的视图的时候,我们可以这样做:
View view = (View)getChildAt(getChildCount()-1);
int d = view.getBottom();
d = (getHeight()+getScrollY());
if(d == 0){
//滑动到底部
}
mItemCount = getAdapter().getCount();
所以getCount()和getView()是一对,存在于适配器Adapter中
总之你要记住:如果你想对可见区域的某一个位置的item操作就使用getChildAt()和getChildCount(),对总的操作就使用adapter里面的getCount()和getView()
6.
6.1实现OnTouchListener来监听是否滑动到最底部
OnTouchListener onTouchListener=new OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
//底部
if (childView != null && childView .getMeasuredHeight() <= getScrollY() + getHeight()) {
//getScrollY() + getHeight() - getPaddingTop()-getPaddingBottom() == getChildAt(0).getMeasuredHeight() 有padding的情况
}
else if (getScrollY() == 0) { //顶部
}
else{
//中间
}
break;
}
return false;
}
}
scroll_view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 判断 scrollView 当前滚动位置在顶部
if(scroll_View.getSrollY == 0){
}
//底部
if (scroll_view.getChildAt(0).getHeight() - scroll_view.getHeight() == scroll_view.getScrollY()){
}
return false;
}
});
6.2 onScrollChanged 监听 --- 更好
public class myScrollView extends ScrollView {
public myScrollView(Context context)
{
super(context);
}
public myScrollView(Context context, AttributeSet attributeSet)
{
super(context,attributeSet);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt)
{
//1
View view = (View)getChildAt(getChildCount()-1);
int d = view.getBottom();
d -= (getHeight()+getScrollY());
if(d==0) {
//
}
else
super.onScrollChanged(l,t,oldl,oldt);
}
四、
//拖动监听,使 scrollview 进行动态变换
使用EditText:在ScrollView包裹的最底部View中加入一个EditText,EditText 获取焦点,自动滚动到最底部
View.OnDragListener roomOnDragListener = new View.OnDragListener() {
@Override
public boolean onDrag(View v, DragEvent event) {
final int action = event.getAction();
switch (action) {
case DragEvent.ACTION_DRAG_STARTED:
todo
if (gridView.getChildCount() % 3 == 0 && gridView.getChildCount() != 0) {
mEtBotton.setVisibility(View.VISIBLE);
mEtBotton.requestFocus();
}
return true;
case DragEvent.ACTION_DRAG_ENDED:
//todo
mEtBotton.setVisibility(View.GONE);
break;
case DragEvent.ACTION_DROP:
// 初始控件
View view = (View) event.getLocalState();
if (view == null) {
return false;
}
// 放落控件
ViewGroup owner = (ViewGroup) view.getParent();
if (owner != null && owner.getId() == R.id.room_type_rv) { // 房间类别拖动
RoomTypeContent roomType = mRoomTypeAdapter.getItem((int) view.getTag());
addRoom(roomType, ctrlNodeId); // 外面添加了数据库更新监听,这儿不用去更新界面了
//todo
mEmptyTV.setVisibility(View.GONE);
} else if (view.getTag() instanceof Room) { // 房间内拖动
...
...
//todo
mEtBotton.setVisibility(View.GONE);
return true;
case DragEvent.ACTION_DRAG_LOCATION:
return true;
}
return false;
}
};
// 如果只有一个节点,左边的所有区域都是
if (ctrlNodes.size() == 1) {
container.setOnDragListener(roomOnDragListener);
} else {
layout.setOnDragListener(roomOnDragListener);
}
五、系统原生提供的方法 和自己判断的区别
1. onScrollChanged方式,自己计算
2. onOverScrolled使用系统计算的结果,api >= 9才支持
可能忽视的细节1:
如果是手势滑动,上面两种方式都对,但是如果是调用ScrollView的smoothScrollTo和scrollTo方法来滚动的话,
只有onScrollChanged监听对,onOverScrolled监听不对,因为通过代码来滚动话是精确滚动,onOverScrolled方法没处理这种情况
惯性滚动监听
public class ListeningScrollView extends ScrollView {
/**
* 上次的Y坐标
*/
private int lastY = 0;
private ScrollYListener scrollYListener;
private ScrollListener scrollListener;
private static final int SCROLL_TIME = 20;
private static final int SCROLL_WHAT = 111;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case SCROLL_WHAT:
int scrollY = getScrollY();
DebugUtils.printLogE("收到惯性滑动事件" + scrollY);
if (lastY != scrollY) {
lastY = scrollY;
scrollYListener.onScrollChanged(scrollY);
handler.sendEmptyMessageDelayed(SCROLL_WHAT, SCROLL_TIME);
}
break;
}
}
};
public ListeningScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setScrollYViewListener(ScrollYListener scrollYListener) {
this.scrollYListener = scrollYListener;
}
public void setScrollViewListener(ScrollListener scrollListener) {
this.scrollListener = scrollListener;
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (scrollYListener != null) {
scrollYListener.onScrollChanged(t);
}
if(scrollListener != null){
scrollListener.onScrollChanged(l,t,oldl,oldt,computeVerticalScrollRange());
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
if (scrollYListener != null) {
handler.sendEmptyMessage(SCROLL_WHAT);
DebugUtils.printLogE("发送滑动事件");
}
break;
}
return super.onTouchEvent(ev);
}
/**
* 带惯性的滑动监听器
*/
public interface ScrollYListener {
void onScrollChanged(int y);
}
/**
* 滑动监听器
*/
public interface ScrollListener {
void onScrollChanged(int x, int y, int oldx, int oldy,int computeVerticalScrollRange);
}
}