前面一篇博文《关于View的ScrollTo,
getScrollX 和 getScrollY》,我们讲解View 的scrollTo() 和 getScrollX() 的功能,也提到了它们其实是一般是配合scroller 类来进行屏幕的滑动的。有的朋友可能会问,为什么有了scrollTo() 之后,还要有scroller 类呢,区别在于,scrollTo() 和 scrollBy() 他们实现的是一个结果,也就是说,当你调用scrollTo(100, 0) 的时候,再重新绘制的时候,内容就已经出现在(100,0)的位置上了,缺少一个过程,而scroller
类就是来帮助我们展现这个滚动的过程的。
动画的原理其实是不停地重绘位置变化的内容,在视觉效果上,就会产生动画的效果。scroller 类的原理其实也正是如此,通过循环地绘制不同位置上的内容,来展现屏幕滚动。
它的原理图大概如下:
这样讲就太抽象了,还是结合例子来看看吧。
拿上次写的自定义ViewGroup, 我们来进行扩展一下,代码如下:
public class CustomRotateViewGroup extends ViewGroup implements OnTouchListener{
private static final int snapVelocity = 200;
private static final int rows = 3;
private static final int padding = 10;
private Scroller mScroller;
private float downX;
private float curX;
private int distanceX;
private int screenWidth;
private VelocityTracker velocityTracker;
private enum Direction{
Current,
Next
}
public CustomRotateViewGroup(Context context) {
super(context);
initialize(context);
}
public CustomRotateViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context);
}
//初始化操作
public void initialize(Context context){
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager windowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
screenWidth = displayMetrics.widthPixels;//获得屏幕宽度
mScroller = new Scroller(context);//获取scroller实例
setOnTouchListener(this);//设置触摸监听
//添加图片
addCustomRotateView(context, R.drawable.photo1, 60, Color.YELLOW);
addCustomRotateView(context, R.drawable.photo2, 30, Color.CYAN);
addCustomRotateView(context, R.drawable.photo3, -30, Color.MAGENTA);
addCustomRotateView(context, R.drawable.photo4, 60, Color.CYAN);
addCustomRotateView(context, R.drawable.photo5, 30, Color.MAGENTA);
addCustomRotateView(context, R.drawable.photo6, -30, Color.LTGRAY);
addCustomRotateView(context, R.drawable.photo3, 60, Color.YELLOW);
addCustomRotateView(context, R.drawable.photo4, 30, Color.LTGRAY);
addCustomRotateView(context, R.drawable.photo5, -30, Color.CYAN);
}
private void addCustomRotateView(Context context, int resId, float degree, int color){
addView(new CustomRotateView(context, resId, degree, color));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int len = getChildCount();
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
parentHeight -= padding * rows;
for(int i = 0; i < len; i++){
View child = getChildAt(i);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(parentHeight / rows, MeasureSpec.AT_MOST);
measureChild(child, widthMeasureSpec, childHeightMeasureSpec);
}
}
//布局函数,将图片从左向右排列,一列3个。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int startX = 0;
int startY = 0;
int len = getChildCount();
boolean flagVertical = true;
for(int i = 0; i <len; i++){
View child = getChildAt(i);
int childHeight = child.getMeasuredHeight();
int childWidth = child.getMeasuredWidth();
child.layout(startX, startY, startX + childWidth, startY + childHeight);
if(flagVertical){
if((i + 1) % rows == 0){
startX += childWidth;
flagVertical = false;
startY = 0;
}else{
startY += childHeight + padding;
}
}else{
flagVertical = true;
startY += childHeight + padding;
}
}
}
//重载View的computeScroll函数
@Override
public void computeScroll(){
if(mScroller.computeScrollOffset()){//滚动是否结束,返回true表明滚动还未结束,同时在函数里获取currX和currY的值
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());//滚动到当前位置
postInvalidate();//强制重新绘制View,在框架里,View 的绘制会重新调用到computeScroll方法,达到循环绘制的目的
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//to add the event to the velocityTracker, in order to calculate the velocity of the slop
if(velocityTracker == null){
velocityTracker = VelocityTracker.obtain();
}
velocityTracker.addMovement(event);
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
//当停止动画的时候,它会马上滚动到终点,然后向动画设置为结束。
if(mScroller != null && !mScroller.isFinished()){
mScroller.abortAnimation();
}
downX = event.getX();
curX = event.getX();
break;
case MotionEvent.ACTION_MOVE:
float moveX = event.getX();
distanceX = (int)(moveX - curX);
scrollBy(-distanceX, 0);
curX = moveX;
break;
case MotionEvent.ACTION_UP:
float upX = event.getX();
distanceX = (int)(upX - downX);
velocityTracker.computeCurrentVelocity(1000);
float vx = velocityTracker.getXVelocity();
if (getScrollX() < 0) { // scroll to right (the first screen)
mScroller.startScroll(getScrollX(), 0, 0 - getScrollX(), 0,1000);
} else if (getScrollX() > screenWidth * (getChildCount() / 3 - 1)) { // scroll to left
scrollToScreen(Direction.Current);
} else {
if (distanceX > 0) { // scroll to left
if (distanceX > screenWidth / 2
|| vx > snapVelocity) {
scrollToScreen(Direction.Current);
} else {
scrollToScreen(Direction.Next);
}
} else if (distanceX < 0) {
if (Math.abs(distanceX) > screenWidth / 2 || vx < -snapVelocity) {
// scroll to right (the next screen)
scrollToScreen(Direction.Next);
} else {
// scroll to left (the current screen)
scrollToScreen(Direction.Current);
}
}
}
velocityTracker.recycle();
invalidate();// 在这里一定要记得调用invalidate 来促使屏幕重绘,开始循环绘制的过程,不然。。。没有入口去滚。
break;
}
return true;
}
private void scrollToScreen(Direction to){
if(to == Direction.Next){
mScroller.startScroll(getScrollX(), 0, screenWidth - getScrollX() % screenWidth, 0, 1000);
}else{
mScroller.startScroll(getScrollX(), 0, - getScrollX() % screenWidth, 0, 1000);
}
}
}
这个自定义的滚动ViewGroup 实现的主要功能如下:
1)首先我们添加了N(在这个例子中是9) 张自定义的图片放在这个ViewGroup里面
2)将这 N 张图片按照 3 个一列,从左向右排,为了易于展示和写代码,每一列都跟屏幕一样宽,这样在界面上的时候,就只看到第一列图片
3)当我们手指向左向右滑动的时候,屏幕会跟随着手指进行滑动
4)当我们手指滑过窗口的二分之一宽或者滑动的速度大于200的时候,手指松开,屏幕会自动滑向下一屏或者上一屏。
5)边界检测,当屏幕滑到最左屏最右屏的时候,再继续滑动,到达黑暗地域的时候,手指松开,会自动滚动回来。
下面是效果Gif:(之前滑动效果做反了,经同事提醒,才发现的确是有点不对劲,=_=!! ---修正于20140112)
最后附上源代码,请点击 源代码下载,希望各位指出需要完善的地方,一起进步。
分享到:
相关推荐
详细讲解自定义ViewGroup+Scroller+VelocityTracker做出Launcher滑动
自定义ViewGroup,根据手势滑动界面
Android ViewDragHelper完全解析 自定义ViewGroup
自定义ViewGroup(高度不同的列表) MIT License Copyright (c) 2017 JiaoZhengXiang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated ...
代码为博客实例代码:http://blog.csdn.net/lmj623565791/article/details/38339817, 有问题请博客留言
自定义ViewGroup学习 最简单的模式,显示一个TextView到自定义ViewGroup中
今天看到很多app都有Title自动隐藏功能,1.这是一个Gradle工程,Gradle的版本是2.10,项目的Compile sdk Version是 23 , Build ...Android自定义ViewGroup实现Title自动隐藏功能源代码分享,Android开发者必看示例。
自定义ViewGroup+Adapter
最近有些空闲时间,我有回顾了一下自定义控件的一些东西,我自定义了一个ViewPager,性能不太好,所以上传到这和大家交流学习
自定义VIewGroup组合控件,包括事件的处理,可以运行,值得学习
自定义ViewGroup实现瀑布流,图片加载,图片回收,跟改列数,滑动加载。
自定义Viewgroup,其中包括三个listview,但拖拽中间listview的上部,3个listview一起运动。
可以自动换行,如果一行放不下,可换行,还可以支持滚动布局。
自定义ViewGroup进一步学习,显示两个TextView到自定义控件中,对两个TextView进行布局
ViewGroup存在的目的就是为了对其子View进行管理,为其子View...因此,自定义ViewGroup通常需要重写onMeasure()方法来对子View进行测量,重写onLayout()方法来确定子View的位置,重写onTouchEvent()方法增加响应事件。
android自定义viewGroup仿Scrollview详解
自定义ViewGroup实现流式布局demo
android自定义viewgroup实现等分格子布局
原文地址http://blog.csdn.net/android_cmos/article/details/52356229
资源为博客的例子,详细讲解参考:http://blog.csdn.net/lmj623565791/article/details/23692439 有问题博客中留言