/**
* The offset, in pixels, by which the content of this view is scrolled
* horizontally.
* {@hide}
*/
protected int mScrollX; //该视图内容相当于视图起始坐标的偏移量 , X轴 方向
/**
* The offset, in pixels, by which the content of this view is scrolled
* vertically.
* {@hide}
*/
protected int mScrollY; //该视图内容相当于视图起始坐标的偏移量 , Y轴方向
/**
* Return the scrolled left position of this view. This is the left edge of
* the displayed part of your view. You do not need to draw any pixels
* farther left, since those are outside of the frame of your view on
* screen.
*
* @return The left edge of the displayed part of your view, in pixels.
*/
public final int getScrollX() {
return mScrollX;
}
/**
* Return the scrolled top position of this view. This is the top edge of
* the displayed part of your view. You do not need to draw any pixels above
* it, since those are outside of the frame of your view on screen.
*
* @return The top edge of the displayed part of your view, in pixels.
*/
public final int getScrollY() {
return mScrollY;
}
public void scrollTo(int x, int y)
/**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the x position to scroll to
* @param y the y position to scroll to
*/
public void scrollTo(int x, int y) {
//偏移位置发生了改变
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x; //赋新值,保存当前便宜量
mScrollY = y;
//回调onScrollChanged方法
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
invalidate(); //一般都引起重绘
}
}
}
public void scrollBy(int x, int y)
/**
* Move the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the amount of pixels to scroll by horizontally
* @param y the amount of pixels to scroll by vertically
*/
// 看出原因了吧 。。 mScrollX 与 mScrollY 代表我们当前偏移的位置 , 在当前位置继续偏移(x ,y)个单位
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
//自定义ViewGroup , 包含了三个LinearLayout控件,存放在不同的布局位置,通过scrollBy或者scrollTo方法切换
public class MultiViewGroup extends ViewGroup {
private Context mContext;
private static String TAG = "MultiViewGroup";
public MultiViewGroup(Context context) {
super(context);
mContext = context;
init();
}
public MultiViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
private void init() {
// 初始化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);
}
// 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(MultiScreenActivity.screenWidth, MultiScreenActivity.scrrenHeight);
}
}
// 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);
child.layout(startLeft, startTop,
startLeft + MultiScreenActivity.screenWidth,
startTop + MultiScreenActivity.scrrenHeight);
startLeft = startLeft + MultiScreenActivity.screenWidth ; //校准每个子View的起始布局位置
//三个子视图的在屏幕中的分布如下 [0 , 320] / [320,640] / [640,960]
}
}
}
package com.qin.customviewgroup;
public class View {
....
protected int mScrollX; //该视图内容相当于视图起始坐标的偏移量 , X轴 方向
protected int mScrollY; //该视图内容相当于视图起始坐标的偏移量 , Y轴方向
//返回值
public final int getScrollX() {
return mScrollX;
}
public final int getScrollY() {
return mScrollY;
}
public void scrollTo(int x, int y) {
//偏移位置发生了改变
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x; //赋新值,保存当前便宜量
mScrollY = y;
//回调onScrollChanged方法
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
invalidate(); //一般都引起重绘
}
}
}
// 看出原因了吧 。。 mScrollX 与 mScrollY 代表我们当前偏移的位置 , 在当前位置继续偏移(x ,y)个单位
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
//...
}
public class Scroller {
private int mStartX; //起始坐标点 , X轴方向
private int mStartY; //起始坐标点 , Y轴方向
private int mCurrX; //当前坐标点 X轴, 即调用startScroll函数后,经过一定时间所达到的值
private int mCurrY; //当前坐标点 Y轴, 即调用startScroll函数后,经过一定时间所达到的值
private float mDeltaX; //应该继续滑动的距离, X轴方向
private float mDeltaY; //应该继续滑动的距离, Y轴方向
private boolean mFinished; //是否已经完成本次滑动操作, 如果完成则为 true
//构造函数
public Scroller(Context context) {
this(context, null);
}
public final boolean isFinished() {
return mFinished;
}
//强制结束本次滑屏操作
public final void forceFinished(boolean finished) {
mFinished = finished;
}
public final int getCurrX() {
return mCurrX;
}
/* Call this when you want to know the new location. If it returns true,
* the animation is not yet finished. loc will be altered to provide the
* new location. */
//根据当前已经消逝的时间计算当前的坐标点,保存在mCurrX和mCurrY值中
public boolean computeScrollOffset() {
if (mFinished) { //已经完成了本次动画控制,直接返回为false
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
float x = (float)timePassed * mDurationReciprocal;
...
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
...
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
//开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达坐标为(startX+dx , startY+dy)出
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX; mStartY = startY;
mFinalX = startX + dx; mFinalY = startY + dy;
mDeltaX = dx; mDeltaY = dy;
...
}
}
public void startScroll(int startX, int startY, int dx, int dy, int duration)
public void startScroll(int startX, int startY, int dx, int dy, int duration)
computeScroll()方法原型如下,该方法位于ViewGroup.java类中
/**
* Called by a parent to request that a child update its values for mScrollX
* and mScrollY if necessary. This will typically be done if the child is
* animating a scroll using a {@link android.widget.Scroller Scroller}
* object.
*/由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制
public void computeScroll() { //空方法 ,自定义ViewGroup必须实现方法体
}
:@Override protected void dispatchDraw(Canvas canvas){
...
for (int i = 0; i < count; i++) {
final View child = children[getChildDrawingOrder(count, i)];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
...
child.computeScroll();
...
}
//自定义ViewGroup , 包含了三个LinearLayout控件,存放在不同的布局位置,通过scrollBy或者scrollTo方法切换
public class MultiViewGroup extends ViewGroup {
...
//startScroll开始移至下一屏
public void startMove(){
curScreen ++ ;
Log.i(TAG, "----startMove---- curScreen " + curScreen);
//使用动画控制偏移过程 , 3s内到位
mScroller.startScroll((curScreen-1) * getWidth(), 0, getWidth(), 0,3000);
//其实点击按钮的时候,系统会自动重新绘制View,我们还是手动加上吧。
invalidate();
//使用scrollTo一步到位
//scrollTo(curScreen * MultiScreenActivity.screenWidth, 0);
}
// 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制
@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 -----");
}
//马上停止移动,如果已经超过了下一屏的一半,我们强制滑到下一个屏幕
public void stopMove(){
Log.v(TAG, "----stopMove ----");
if(mScroller != null){
//如果动画还没结束,我们就按下了结束的按钮,那我们就结束该动画,即马上滑动指定位置
if(!mScroller.isFinished()){
int scrollCurX= mScroller.getCurrX() ;
//判断是否超过下一屏的中间位置,如果达到就抵达下一屏,否则保持在原屏幕
// 这样的一个简单公式意思是:假设当前滑屏偏移值即 scrollCurX 加上每个屏幕一半的宽度,除以每个屏幕的宽度就是
// 我们目标屏所在位置了。 假如每个屏幕宽度为320dip, 我们滑到了500dip处,很显然我们应该到达第二屏,索引值为1
//即(500 + 320/2)/320 = 1
int descScreen = ( scrollCurX + getWidth() / 2) / getWidth() ;
Log.i(TAG, "-mScroller.is not finished scrollCurX +" + scrollCurX);
Log.i(TAG, "-mScroller.is not finished descScreen +" + descScreen);
mScroller.abortAnimation();
//停止了动画,我们马上滑倒目标位置
scrollTo(descScreen *getWidth() , 0);
curScreen = descScreen ; //纠正目标屏位置
}
else
Log.i(TAG, "----OK mScroller.is finished ---- ");
}
}
...
}
//自定义ViewGroup , 包含了三个LinearLayout控件,存放在不同的布局位置,通过scrollBy或者scrollTo方法切换
public class MultiViewGroup extends ViewGroup {
private static String TAG = "MultiViewGroup";
private int curScreen = 0 ; //当前屏幕
private Scroller mScroller = null ; //Scroller对象实例
public MultiViewGroup(Context context) {
super(context);
mContext = context;
init();
}
public MultiViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
//初始化
private void init() {
...
//初始化Scroller实例
mScroller = new Scroller(mContext);
// 初始化3个 LinearLayout控件
...
//初始化一个最小滑动距离
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
// 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制
@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 -----");
}
//两种状态: 是否处于滑屏状态
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 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){
super.onTouchEvent(event);
Log.i(TAG, "--- onTouchEvent--> " );
// TODO Auto-generated method stub
Log.e(TAG, "onTouchEvent start");
//获得VelocityTracker对象,并且添加滑动对象
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
//触摸点
float x = event.getX();
float y = event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
//如果屏幕的动画还没结束,你就按下了,我们就结束上一次动画,即开始这次新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);//开始缓慢滑屏咯。 detaX > 0 向右滑动 , 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();
}
//回收VelocityTracker对象
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
//修正mTouchState值
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() + MultiScreenActivity.screenWidth / 2 ) / MultiScreenActivity.screenWidth ;
Log.e(TAG, "### onTouchEvent ACTION_UP### dx destScreen " + destScreen);
snapToScreen(destScreen);
}
//真正的实现跳转屏幕的方法
private void snapToScreen(int whichScreen){
//简单的移到目标屏幕,可能是当前屏或者下一屏幕
//直接跳转过去,不太友好
//scrollTo(mLastScreen * MultiScreenActivity.screenWidth, 0);
//为了友好性,我们在增加一个动画效果
//需要再次滑动的距离 屏或者下一屏幕的继续滑动距离
curScreen = whichScreen ;
//防止屏幕越界,即超过屏幕数
if(curScreen > getChildCount() - 1)
curScreen = getChildCount() - 1 ;
//为了达到下一屏幕或者当前屏幕,我们需要继续滑动的距离.根据dx值,可能想左滑动,也可能像又滑动
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,所以此时需要手动刷新View 否则没效果
invalidate();
}
//开始滑动至下一屏
public void startMove(){
...
}
//理解停止滑动
public void stopMove(){
...
}
// measure过程
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
}
// layout过程
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
...
}
}
机械节能产品生产企业官网模板...
大气智能家居家具装修装饰类企业通用网站模板...
礼品公司网站模板
宽屏简约大气婚纱摄影影楼模板...
蓝白WAP手机综合医院类整站源码(独立后台)...苏ICP备2024110244号-2 苏公网安备32050702011978号 增值电信业务经营许可证编号:苏B2-20251499 | Copyright 2018 - 2025 源码网商城 (www.ymwmall.com) 版权所有