技术点:
一、全屏显示图片页面
往自定义viewGrou——myViewPager中添加ImageView。
for (int i=0;i<ids.length;i++){
ImageView childView = new ImageView(this);
childView.setBackgroundResource(ids[i]);
//添加到MyViewPager中,但是不会显示,因为viewgroup要重写onLayout
myViewPager.addView(childView);
}
但是此时运行的话,ImageView不显示,是因为viewGroup需要重写ouLayout来指定子view位置。
protected void onLayout(boolean flag, int l, int t, int r, int b) {
//遍历每个孩子,给每个孩子指定在屏幕的坐标位置
for (int index =0; index<getChildCount();index++){
View childView = getChildAt(index);
childView.layout(index*getWidth(),0,(index+1)*getWidth(),getHeight());
}
}
希望实现的效果是,每张图片都以水平方向,一张一张排列全屏显示,如下:
所以第一张的(l,t)坐标是(0,0),(r,b)坐标是(getWidth(),getHeight()),以此类推第二第三张等等。
所以就可以套用公式:(indexgetWidth(),0)得出(l,t)坐标,((index+1)getWidth(),getHeight())得出(r,b)坐标。
二、显示ViewGroup页面
//增加一个ViewGroup的测试页面
View testView = View.inflate(this,R.layout.test,null);
myViewPager.addView(testView,3);
但是这样增加了testView,testView是本事是显示了,但是它里面的子View并不会显示。
问题1.为什么子View 没有显示?
-因为没有测量
问题2.为什么没有测量,ImageView也能显示?
因为给myViewPager的一级子view(7个页面),人为指定位置,绘制交给系统处理。而子view里面的子view并没有指定位置,则需要测量。
/**
* 父页面测量孩子,孩子又测量其孩子
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
for (int i=0; i<getChildCount();i++){
View childView = getChildAt(i);
childView.measure(widthMeasureSpec,heightMeasureSpec);
}
}
最终显示上图结果。
测量OnMeasure()过程:
三、页面滑动
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
//记录点击坐标
tempX = startXByOnTouchEvent = event.getX();
break;
case MotionEvent.ACTION_MOVE:
//记录移动一小段距离的结束坐标
float endXByOnTouchEvent = event.getX();
//滚动这一段距离,scrollBy方向相反,所以取负
scrollBy(-(int) (endXByOnTouchEvent - tempX),0);
//将结束坐标设为开始坐标
tempX = endXByOnTouchEvent;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
但是这样只能单纯的移动,并没有viewpager页面切换的效果。所以需要计算松开手后的移动距离来判断是否滑动到下一页或上一页或留在当前页。
当往左滑动距离大于屏幕的1/2时,则显示下一张。
当往右滑动距离大于屏幕的1/2时,则显示上一张。
case MotionEvent.ACTION_UP:
//记录结束移动的坐标
float realEndX = event.getX();
int tempIndex = currentIndex;
//向左滑,显示下一个页面
if ((startXByOnTouchEvent - realEndX) >getWidth()/2){
tempIndex++;
}else if ((realEndX - startXByOnTouchEvent) >getWidth()/2){ //向右滑,显示上一张
tempIndex--;
}
//根据下标位置移动到指定的页面
scrollToPager(tempIndex);
break;
/**
* 跳转到指定页面
* @param tempIndex
*/
public void scrollToPager(int tempIndex) {
//屏蔽非法值
if (tempIndex<0){
tempIndex = 0;
}
if (tempIndex>getChildCount() - 1){
tempIndex = getChildCount() - 1;
}
//当前的坐标位置
currentIndex = tempIndex;
//根据位置移动到指定页面
scrollTo(currentIndex*getWidth(),0);
}
但是因为scrollTo是直接定位到坐标点,停留在当前页时没有回弹效果,跳转也非常生硬,所以需要为滑动页面时增加回调效果。
四、回弹效果
实现思路:
1)通过即将显示页面的(l,t)坐标的l值(startX)-左上角(l,t)坐标的l值得出需要回弹的总距离backDistanceX。
2)再设置回弹时间totaltime为500毫米
3)计算速度 = backDistanceX/totaltime
4)绿色线段表示页面在一段时间内(passtime)移动的距离distanceSmallX。
5)计算这段距离distanceSmallX = passtime*速度。
6)得出每条线段的坐标(l,t)中的l ,currX= distanceSmallX +startX
7)一直刷新当前的currX,知道滚动完毕。
/**
* 跳转到指定页面
* @param tempIndex
*/
public void scrollToPager(int tempIndex) {
//屏蔽非法值
if (tempIndex<0){
tempIndex = 0;
}
if (tempIndex>getChildCount() - 1){
tempIndex = getChildCount() - 1;
}
//当前的坐标位置
currentIndex = tempIndex;
//根据位置移动到指定页面
// scrollTo(currentIndex*getWidth(),0);
//回弹总距离(backDistanceX) = 显示页面的(l,t)坐标的l - 移动后左上角坐标的l,回弹距离为-时向右回弹
int backDistanceX = currentIndex* getWidth() - getScrollX();
scroller.startScroller(getScrollX(),getScrollY(),backDistanceX,0);
//刷新,onDraw()和computeScroll()方法执行
invalidate();
}
@Override
public void computeScroll() {
// super.computeScroll();
if (scroller.cuputeScroller()){
float currX = scroller.getCurrX();
scrollTo((int) currX,0);
invalidate();
}
}
public class MyScroller {
/**
* 起始坐标
*/
private float startX;
private float startY;
/**
* 在x轴的移动距离
*/
private int distanceX;
/**
* 在y轴的移动距离
*/
private int distanceY;
/**
* 开始时间
*/
private long startTime;
/**
* 是否移动完成
* true:完成
*/
private boolean isFinish;
/**
* 总的移动时间
*/
private long totalTime = 500;
/**
* 移动的当前位置
*/
private float currX ;
/**
* 得到当前坐标
* @return
*/
public float getCurrX(){
return currX;
}
public void startScroller(float startX, float startY, int distanceX, int distanceY) {
this.startX = startX;
this.startY = startY;
this.distanceX = distanceX;
this.distanceY = distanceY;
this.startTime = SystemClock.uptimeMillis();//系统开机时间
this.isFinish = false;
}
/**
* true:正在移动
* false:移动结束
* @return
*/
public boolean cuputeScroller(){
if (isFinish){
return false;
}
long endTime = SystemClock.uptimeMillis();
long passTime = endTime - startTime ;
if (passTime<totalTime){
//还没移动结束
//计算平均速度 = 移动总距离/总时间
/*float voleCity = distanceX/totalTime;*/
//distanceSmallX是当前移动到的位置的l和startX的l的距离
// 移动这一小段的距离 = 时间*速度
float distanceSmallX = passTime * distanceX/totalTime;
currX = startX + distanceSmallX;
}else{
//移动结束
isFinish = true;
currX = startX + distanceX;
}
return true;
}
}
五、滑动冲突/分发机制
问题:在scrollview里面左右滑动没有效果,只能上下滑动。
-因为点击事件在scrollview里面被消耗了,没办法回传。
解决办法:在MyViewPager里面设置拦截onInterceptTouchEvent,判断是左右滑动还是上下滑动,如果是左右滑动则return true,拦截事件触发onTouch方法,否则return false,不拦截事件,传给scroller处理。
/**
* 如果当前方法,返回true,拦截时间,将会触发当前控件的onTouchEvent()方法
* 如果当前方法,返回false,不拦截时间,事件继续传递给孩子
* @param event
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean result = false;//默认传给孩子
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
//记录坐标
startXByOnTouchEvent = startXByOnInterceptTouchEvent = event.getX();
startYByOnInterceptTouchEvent = event.getY();
Log.d("TEST","onInterceptTouchEvent == ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
//如果左右滑动的距离>上下滑动则是左右滑动,方法返回true,拦截事件,触发onTouch
//如果上下滑动的距离>左右滑动则是上下滑动,方法返回false,不拦截事件,将事件传递给子视图
float endX = event.getX();
float endY = event.getY();
float distanceX = Math.abs(endX - startXByOnInterceptTouchEvent);
float distanceY = Math.abs(endY - startYByOnInterceptTouchEvent);
//水平方向
if (distanceX > distanceY && distanceX >5){
result = true;
}
//这里tempX = startXByOnTouchEvent = startXByOnInterceptTouchEvent = endX;
//是因为在scrollview里面左右滑动时方法调用顺序是:
// onInterceptTouchEvent == ACTION_DOWN ——>onInterceptTouchEvent == ACTION_MOVE ——>onTouchEvent == ACTION_MOVE ——>onTouchEvent == ACTION_UP
//并没有调用onTouchEvent == ACTION_DOWN,所以不能重新刷新startXByOnTouchEvent的值,所以tempX需要重新刷新,否则在移动时会出现页面闪动,因为移动距离突然增大
tempX = startXByOnTouchEvent = startXByOnInterceptTouchEvent = endX;
startYByOnInterceptTouchEvent = endY;
Log.d("TEST","onInterceptTouchEvent == ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d("TEST","onInterceptTouchEvent == ACTION_UP");
break;
}
return result;
}
分发机制说明:
View分发:
ViewGroup分发:
六、与radioGroup结合/回调
回调接口步骤:
1.定义接口
public interface OnPageListener{
void getPage(int index);
}
private OnPageListener onPageListener;
2.传递接口实例
public void setOnPageListener(OnPageListener onPageListener) {
this.onPageListener = onPageListener;
}
3.调用方法
if (null != onPageListener){
onPageListener.getPage(currentIndex);
}
4.回调
myViewPager.setOnPageListener(new MyViewPager.OnPageListener() {
@Override
public void getPage(int index) {
radioGroup.check(index);
}
});