写在前面的
参考其他大神的作品,搬过来自己按照自己想要的效果改了改,作为新手这些问题是在恼火。
在运用ScrollViewContainer的时候,感觉真心不错,但是有一个问题,就是我在下面的scrollview里面,怎么到顶部呢?
查了不少资料,没有得到答案,相关的额全部是在求这一类的解决办法,后来我想了想,另辟捷径,做了一个比较水的效果,
希望大家看了,有改善的的意见或建议都提出来,用意完善这一个控件。
- 思路:
在内部无法实现这样的方法,那么可以用外部刺激来完成吗? - 试验1:
ScrollViewContainer针对父容器到顶部。 结果:失败,原因(当ScrollViewContainer被分页之后,虽然是容器,但是焦点都在当前的scrollview,所以无法实现) - 试验2:
隐藏底部的scrollview。。。。。(当然很蠢的办法,大家无视掉) - 试验3:
这个是突然地灵光一闪,手指滑动可以翻页,内部的构造方法不行,那么我能不能模拟手势滑动呢?
这么一想,可怕,于是就这么天真的行了。代码如下:
MainActivity
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private TextView top_text;
private ScrollViewContainer all_scrollView;// 核心部分,为自定义的scrollview容器
private ScrollView scr1, scr2;// 两个scrollview
boolean gh = true;// 点击标识。防止多次点击
boolean ScrollViewConcanPullUp, ScrollViewConcanPullDown;// 翻页标识(后面会解释其用意)
public static TextView title;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
title = (TextView) findViewById(R.id.title);
top_text = (TextView) findViewById(R.id.top_text);
all_scrollView = (ScrollViewContainer) findViewById(R.id.all_scrollView);
scr1 = (ScrollView) findViewById(R.id.scr1);
scr2 = (ScrollView) findViewById(R.id.scr2);
// 到顶部的View点击事件
top_text.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (!gh) {
return;
}
gh = false;
ScrollViewConcanPullUp = ScrollViewContainer.canPullUp;// 得到控件的翻页标识
ScrollViewConcanPullDown = ScrollViewContainer.canPullDown;
ScrollViewContainer.canPullDown = true;// 强制设置为true(true为可翻页,false反之)
ScrollViewContainer.canPullUp = true;
handler2.sendEmptyMessage(22);// 异步触发已检测事件(检测似乎翻页成功)
scr2.scrollTo(0, 0);// 在下面的scrollview里面,先执行到底部的scrollview顶部
SystemClock.sleep(50);// 等待时间(快速到底部,有一个惯性效果,如果马上触发scrollview,会有一个阻尼效果,导致不能翻页成功,所以,最后采取等一段时间scrollview自动恢复)
RunStart(0);// 翻页步骤(翻页完成的之后,顶部的scrollview再到顶部,整个流程就算完成了)
}
});
}
/**
* 滑动手势模拟(到顶部)
*
* @param u
*/
private void RunStart(final int u) {
SystemClock.sleep(50);
new Thread() {
@Override
public void run() {
final long downTime = SystemClock.currentThreadTimeMillis();
runOnUiThread(new Runnable() {
@Override
public void run() {
MotionEvent down = MotionEvent.obtain(downTime, SystemClock.currentThreadTimeMillis(), MotionEvent.ACTION_DOWN, 100, 80, 0);// 点击模拟
all_scrollView.dispatchTouchEvent(down);
}
});
SystemClock.sleep(50);
runOnUiThread(new Runnable() {
@Override
public void run() {
MotionEvent move = MotionEvent.obtain(downTime, SystemClock.currentThreadTimeMillis(), MotionEvent.ACTION_MOVE, 100, 400, 0);// 滑动模拟
all_scrollView.dispatchTouchEvent(move);
}
});
SystemClock.sleep(150);
runOnUiThread(new Runnable() {
@Override
public void run() {
MotionEvent up = MotionEvent.obtain(downTime, SystemClock.currentThreadTimeMillis(), MotionEvent.ACTION_UP, 100, 400, 0);// 手指抬起(模拟事件完成)
all_scrollView.dispatchTouchEvent(up);
}
});
}
}.start();
if (u == 2) {// 滑动事件执行完成。不论是否翻页成功,都把缓存起来的标识给还回去
ScrollViewContainer.canPullUp = ScrollViewConcanPullUp;
ScrollViewContainer.canPullDown = ScrollViewConcanPullDown;
scr1.scrollTo(0, 0);// 上部份scrollview到顶部(需要动画流畅的,建议这一步放到检测是否成功的方法里面去,即放在翻页成功里面去,效果看起来会更连贯)
}
if (u == 0) {
SystemClock.sleep(50);
RunStart(2);
}
}
Handler handler2 = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 22) {
handler2.postAtTime(new Runnable() {
@Override
public void run() {
System.out.println("ScrollViewContainer.mCurrentViewIndex__>" + ScrollViewContainer.mCurrentViewIndex);
if (ScrollViewContainer.mCurrentViewIndex == 0) {// mCurrentViewIndex+> 0代表上部分的scrollview 1 代表下半部分的scrollview
System.out.println("翻页完成");
} else {
System.out.println("翻页失败");// 翻页失败 (这里可以做一些失败的操作,比如提示一下,翻页失败什么的)
Toast.makeText(MainActivity.this, "翻页失败", 0).show();
System.out.println("ScrollViewContainer__>" + ScrollViewContainer.mCurrentViewIndex);
}
gh = true; 点击标识。防止多次点击,等到此次事件完成,才能触发下一次的翻页
}
}, SystemClock.uptimeMillis() + 650);
}
};
};
}
ScrollViewContainer
import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
/**
* 包含两个ScrollView的容器
*/
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 static VelocityTracker vt;
private int mViewHeight;
private int mViewWidth;
private View topView;
private View bottomView;
public static boolean canPullDown;
public static boolean canPullUp;
private static int state = DONE;
/**
* 记录当前展示的是哪个view,0是topView,1是bottomView
*/
public static int mCurrentViewIndex = 0;
/**
* 手滑动距离,这个是控制布局的主要变量
*/
private float mMoveLen;
private static 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;
System.out.println("1___________+_>" + mCurrentViewIndex);
MainActivity.title.setText("你在第二页 可以试一试到顶部");
}
} else if (state == AUTO_DOWN) {
mMoveLen += SPEED;
if (mMoveLen >= 0) {
mMoveLen = 0;
state = DONE;
mCurrentViewIndex = 0;
System.out.println("2_______________+_>" + mCurrentViewIndex);
MainActivity.title.setText("你在第一页 向下滑动到第二页");
}
} 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:
// 多一只手指按下或抬起时舍弃将要到来的第一个事件move,防止多点拖拽的bug
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);
// 获取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();
// } catch (IllegalStateException e) {
// e.printStackTrace();
// }
break;
}
try {
super.dispatchTouchEvent(ev);
} catch (Exception e) {
}
return true;
}
@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());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!isMeasured) {
isMeasured = true;
mViewHeight = getMeasuredHeight();// - dp2px(60)
mViewWidth = getMeasuredWidth();
topView = getChildAt(0);
bottomView = getChildAt(1);
bottomView.setOnTouchListener(bottomViewTouchListener);
topView.setOnTouchListener(topViewTouchListener);
}
}
private OnTouchListener topViewTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
ScrollView sv = (ScrollView) v;
if (sv.getScrollY() == (sv.getChildAt(0).getMeasuredHeight() - sv.getMeasuredHeight()) && 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;
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();
}
}
}
// 将dp转化为px
private int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
}
AndroidManifest.xml
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name">
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>