package com.qin.scrollerview;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Scroller;
import android.widget.TextView;
//自定義ViewGroup , 包含了三個LinearLayout控制元件,存放在不同的佈局位置,通過scrollBy或者scrollTo方法切換
public class MultiViewGroup extends ViewGroup {
private Context mContext;
private static String TAG = "MultiViewGroup";
private int curScreen = 0 ; //當前屏
private Scroller mScroller = null ;
public MultiViewGroup(Context context) {
super(context);
mContext = context;
init();
}
public MultiViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
//startScroll 滑屏
public void startMove(){
curScreen ++ ;
Log.i(TAG, "----startMove---- curScreen " + curScreen);
Log.i(TAG, "----width " + getWidth());
//採用Scroller類控制滑動過程
mScroller.startScroll((curScreen-1) *getWidth(), 0,
getWidth(), 0,3000);
//暴力點直接到目標出
//scrollTo(curScreen * getWidth(), 0);
//其實在點選按鈕的時候,就回觸發View繪製流程,這兒我們在強制繪製下View
invalidate();
}
//停止滑屏
public void stopMove(){
Log.v(TAG, "----stopMove ----");
if(mScroller != null){
//如果動畫還沒結束,我們就按下了結束的按鈕,那我們就結束該動畫,即馬上滑動指定位置
if(!mScroller.isFinished()){
int scrollCurX= mScroller.getCurrX() ;
//判斷是否達到下一屏的中間位置,如果達到就抵達下一屏,否則保持在原螢幕
//int moveX = scrollCurX - mScroller.getStartX() ;
// Log.i(TAG, "----mScroller.is not finished ---- shouldNext" + shouldNext);
//boolean shouldNext = moveX >= getWidth() / 2 ;
int descScreen = ( scrollCurX + getWidth() / 2) / getWidth() ;
Log.i(TAG, "----mScroller.is not finished ---- shouldNext" + descScreen);
Log.i(TAG, "----mScroller.is not finished ---- scrollCurX " + scrollCurX);
mScroller.abortAnimation();
//停止了動畫,我們馬上滑倒目標位置
scrollTo(descScreen * getWidth() , 0);
mScroller.forceFinished(true);
curScreen = descScreen ;
}
}
else
Log.i(TAG, "----OK mScroller.is finished ---- ");
}
// 只有當前LAYOUT中的某個CHILD導致SCROLL發生滾動,才會致使自己的COMPUTESCROLL被呼叫
@Override
public void computeScroll() {
// TODO Auto-generated method stub
Log.e(TAG, "computeScroll");
// 如果返回true,表示動畫還沒有結束
// 因為前面startScroll,所以只有在startScroll完成時 才會為false
if (mScroller.computeScrollOffset()) {
Log.e(TAG, mScroller.getCurrX() + "======" + mScroller.getCurrY());
// 產生了動畫效果 每次滾動一點
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
Log.e(TAG, "### getleft is " + getLeft() + " ### getRight is " + getRight());
//重新整理View 否則效果可能有誤差
postInvalidate();
}
else
Log.i(TAG, "have done the scoller -----");
}
/////以上可以演示Scroller類的使用
//// --------------------------------
/////--------------------------------
private static final int TOUCH_STATE_REST = 0;
private static final int TOUCH_STATE_SCROLLING = 1;
private int mTouchState = TOUCH_STATE_REST;
//--------------------------
//處理觸控事件 ~
public static int SNAP_VELOCITY = 600 ;
private int mTouchSlop = 0 ;
private float mLastionMotionX = 0 ;
private float mLastMotionY = 0 ;
//處理觸控的速率
private VelocityTracker mVelocityTracker = null ;
// 這個感覺沒什麼作用 不管true還是false 都是會執行onTouchEvent的 因為子view裡面onTouchEvent返回false了
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
Log.e(TAG, "onInterceptTouchEvent-slop:" + mTouchSlop);
final int action = ev.getAction();
//表示已經開始滑動了,不需要走該Action_MOVE方法了(第一次時可能呼叫)。
if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
return true;
}
final float x = ev.getX();
final float y = ev.getY();
switch (action) {
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onInterceptTouchEvent move");
final int xDiff = (int) Math.abs(mLastionMotionX - x);
//超過了最小滑動距離
if (xDiff > mTouchSlop) {
mTouchState = TOUCH_STATE_SCROLLING;
}
break;
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onInterceptTouchEvent down");
mLastionMotionX = x;
mLastMotionY = y;
Log.e(TAG, mScroller.isFinished() + "");
mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
Log.e(TAG, "onInterceptTouchEvent up or cancel");
mTouchState = TOUCH_STATE_REST;
break;
}
Log.e(TAG, mTouchState + "====" + TOUCH_STATE_REST);
return mTouchState != TOUCH_STATE_REST;
}
public boolean onTouchEvent(MotionEvent event){
Log.i(TAG, "--- onTouchEvent--> " );
// TODO Auto-generated method stub
Log.e(TAG, "onTouchEvent start");
if (mVelocityTracker == null) {
Log.e(TAG, "onTouchEvent start-------** VelocityTracker.obtain");
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
super.onTouchEvent(event);
//手指位置地點
float x = event.getX();
float y = event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
//如果螢幕的動畫還沒結束,你就按下了,我們就結束該動畫
if(mScroller != null){
if(!mScroller.isFinished()){
mScroller.abortAnimation();
}
}
mLastionMotionX = x ;
break ;
case MotionEvent.ACTION_MOVE:
int detaX = (int)(mLastionMotionX - x );
scrollBy(detaX, 0);
Log.e(TAG, "--- MotionEvent.ACTION_MOVE--> detaX is " + detaX );
mLastionMotionX = x ;
break ;
case MotionEvent.ACTION_UP:
final VelocityTracker velocityTracker = mVelocityTracker ;
velocityTracker.computeCurrentVelocity(1000);
int velocityX = (int) velocityTracker.getXVelocity() ;
Log.e(TAG , "---velocityX---" + velocityX);
//滑動速率達到了一個標準(快速向右滑屏,返回上一個螢幕) 馬上進行切屏處理
if (velocityX > SNAP_VELOCITY && curScreen > 0) {
// Fling enough to move left
Log.e(TAG, "snap left");
snapToScreen(curScreen - 1);
}
//快速向左滑屏,返回下一個螢幕)
else if(velocityX < -SNAP_VELOCITY && curScreen < (getChildCount()-1)){
Log.e(TAG, "snap right");
snapToScreen(curScreen + 1);
}
//以上為快速移動的 ,強制切換螢幕
else{
//我們是緩慢移動的,因此先判斷是保留在本螢幕還是到下一螢幕
snapToDestination();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
mTouchState = TOUCH_STATE_REST ;
break;
case MotionEvent.ACTION_CANCEL:
mTouchState = TOUCH_STATE_REST ;
break;
}
return true ;
}
////我們是緩慢移動的
private void snapToDestination(){
//當前的偏移位置
int scrollX = getScrollX() ;
int scrollY = getScrollY() ;
Log.e(TAG, "### onTouchEvent snapToDestination ### scrollX is " + scrollX);
//判斷是否超過下一屏的中間位置,如果達到就抵達下一屏,否則保持在原螢幕
//直接使用這個公式判斷是哪一個螢幕 前後或者自己
//判斷是否超過下一屏的中間位置,如果達到就抵達下一屏,否則保持在原螢幕
// 這樣的一個簡單公式意思是:假設當前滑屏偏移值即 scrollCurX 加上每個螢幕一半的寬度,除以每個螢幕的寬度就是
// 我們目標屏所在位置了。 假如每個螢幕寬度為320dip, 我們滑到了500dip處,很顯然我們應該到達第二屏
int destScreen = (getScrollX() + getWidth() / 2 ) / getWidth() ;
Log.e(TAG, "### onTouchEvent ACTION_UP### dx destScreen " + destScreen);
snapToScreen(destScreen);
}
private void snapToScreen(int whichScreen){
//簡單的移到目標螢幕,可能是當前屏或者下一螢幕
//直接跳轉過去,不太友好
//scrollTo(mLastScreen * getWidth(), 0);
//為了友好性,我們在增加一個動畫效果
//需要再次滑動的距離 屏或者下一螢幕的繼續滑動距離
curScreen = whichScreen ;
if(curScreen > getChildCount() - 1)
curScreen = getChildCount() - 1 ;
int dx = curScreen*getWidth() - getScrollX() ;
Log.e(TAG, "### onTouchEvent ACTION_UP### dx is " + dx);
mScroller.startScroll(getScrollX(), 0, dx, 0,Math.abs(dx) * 2);
//此時需要手動重新整理View 否則沒效果
invalidate();
}
private void init() {
mScroller = new Scroller(mContext);
// 初始化3個 LinearLayout控制元件
LinearLayout oneLL = new LinearLayout(mContext);
oneLL.setBackgroundColor(Color.RED);
addView(oneLL);
LinearLayout twoLL = new LinearLayout(mContext);
twoLL.setBackgroundColor(Color.YELLOW);
addView(twoLL);
LinearLayout threeLL = new LinearLayout(mContext);
threeLL.setBackgroundColor(Color.BLUE);
addView(threeLL);
//初始化一個最小滑動距離
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
// measure過程
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.i(TAG, "--- start onMeasure --");
// 設定該ViewGroup的大小
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
int childCount = getChildCount();
Log.i(TAG, "--- onMeasure childCount is -->" + childCount);
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
// 設定每個子檢視的大小 , 即全屏
child.measure(getWidth(), MultiScreenActivity.scrrenHeight);
}
}
private int curPage = 0 ;
// layout過程
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
Log.i(TAG, "--- start onLayout --");
int startLeft = 0; // 每個子檢視的起始佈局座標
int startTop = 10; // 間距設定為10px 相當於 android:marginTop= "10px"
int childCount = getChildCount();
Log.i(TAG, "--- onLayout childCount is -->" + childCount );
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
//即使可見的,才劃到螢幕上
if(child.getVisibility() != View.GONE)
child.layout(startLeft, startTop,
startLeft + getWidth(),
startTop + MultiScreenActivity.scrrenHeight );
startLeft = startLeft + getWidth() ; //校準每個子View的起始佈局位置
//三個子檢視的在螢幕中的分佈如下 [0 , 320] / [320,640] / [640,960]
}
}
}