我们先来看看优酷的控件是怎么回事?

仅仅响应最后也就是最顶部的卡片的点击事件,假设点击的不是最顶部的卡片那么就先把它放到最顶部。然后在移动到最前面来。重复如次。

知道了这几条那么我们就非常好做了。

里面的技术细节可能就是child的放置到前面来的动画问题把。

先看看我们实现得效果:

然后细致分析一下我们要实现怎么样的效果:

我也是放置了一个button和两个view在控件上面。仅仅有当控件在最前面也就是最里面的时候才会响应事件。

然后我们就动手来实现这个控件。

我们继承一个ViewGroup而且命名为ExchageCarldView。最開始的当然是它的onMeasure和onLayout方法了。这里贴出代码然后一一解说。

        @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
measureChildren(widthMeasureSpec, heightMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
        @Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
int count = getChildCount();
if (mIsExchageAnimation) // 动画路径
{
for (int i = 0; i < count; i++)
{
if (mTouchIndex > i) // 当点击的头部以上的不须要改变layout
continue;
View view = getChildAt(i);
// 缓存第一次view的信息。就是动画刚開始的信息
cacheViewTopAndBottomIfneed(i, view);
if (count - 1 == i) // 最上层的布局
{
// 计算它究竟该走多少高度总高度
int total_dis = view.getHeight() / 2 * (count - 1 - mTouchIndex);
// 计算当前的线性距离
int now_dis = (int) (total_dis * (System.currentTimeMillis() - mAnimationStartTime) / Default_animtion_time);
// 回归不能超过total_dis这个值
int dis = Math.min(now_dis, total_dis); view.layout(view.getLeft(), mViewsTopCache.get(i) + dis, view.getRight(), mViewsBottomCache.get(i) + dis);
}
else
{
// 除去最上层的那个那个布局
// 每一个卡片都应该移动view.height的1/2
int total_dis = view.getHeight() / 2;
// 计算当前的线性距离
int now_dis = (int) (total_dis * (System.currentTimeMillis() - mAnimationStartTime) / Default_animtion_time);
// 回归不能超过total_dis这个值
int dis = Math.min(now_dis, total_dis);
// 放置布局的位置
view.layout(view.getLeft(), mViewsTopCache.get(i) - dis, view.getRight(), mViewsBottomCache.get(i) - dis);
} // 检測动画是否结束
checkAnimation();
}
}
else
{
// 初始化的时候初始化我们的卡片
mTotalHight = 0;
for (int i = 0; i < count; i++)
{
View view = getChildAt(i);
view.layout(getPaddingLeft(), mTotalHight, view.getMeasuredWidth(), mTotalHight + view.getMeasuredHeight());
mTotalHight += view.getMeasuredHeight() / 2; // 这里取的是一半的布局
}
}
}

能够看到在onMeasure方法里面我什么也没做,仅仅是调用了自带的測量方法,最基本的就是在onlayout这种方法里面了,能够看到它有两个分支。一个分支是当他动画的时候调用的分支。一个是精巧的时候调用的分支。能够看到。我这里取的是高度的一半来作为遮盖的地方。当然可能还有人问我为什么我这里要用layout来做动画呢?这里我先不解答这个问题,先跟着往以下走。里面有个缓存的函数,我们来还是先贴出来。

/**
* 缓存view的顶部和底部信息
*
* @param i
* @param view
*/
void cacheViewTopAndBottomIfneed(int i, View view)
{
int viewtop = mViewsTopCache.get(i, -1); if (viewtop == -1)
{
mViewsTopCache.put(i, view.getTop());
} int viewbttom = mViewsBottomCache.get(i, -1);
if (viewbttom == -1)
{
mViewsBottomCache.put(i, view.getBottom());
}
}

为什么我们须要缓存这个?由于在重复的调用layout的时候我们去调用gettop等方法获取的每次都会变化没有一个对齐的点,所以我们须要缓存一下開始移动的初始化位置。

位置都放置好了那么我们就能够来看看我们的Touch事件是怎么处理的了。

贴上我们的代码

	@Override
public boolean dispatchTouchEvent(MotionEvent event)
{
if (mIsExchageAnimation) // 当有动画的时候我们吃掉这个事件
return true;
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
mTouchIndex = getTouchChildIndex(event); // 获取点击视图的index
if (mTouchIndex != -1 && mTouchIndex != getChildCount() - 1) // 点击的是最后的一个的时候不用开启动画
{
startAnimation();
}
}
// return super.dispatchTouchEvent(event);
// // 仅仅响应最后一个卡片的点击的事件
if (mTouchIndex == getChildCount() - 1)
{
return super.dispatchTouchEvent(event);
}
else
{
// 其它的点击事件吃掉
return true;
}
}

这里的代码也非常easy就是在点击的时候推断是不是在动画假设在动画就返回,然后获取到点击的child的index调用startAnimation开启动画。后面的推断就是推断仅仅对应最后一个卡片的点击事件。

以下也挨着来看看其它两个函数的代码。

/***
* 依据点击的事件获取child的index
*
* @param event
* @return
*/
int getTouchChildIndex(MotionEvent event)
{
for (int i = 0; i < getChildCount(); i++)
{
View view = getChildAt(i);
Rect r = new Rect();
view.getGlobalVisibleRect(r);
r = new Rect(r.left, r.top, r.right, r.bottom - view.getHeight() / 2); // 须要注意的是这里我们是取的上半部分来做点推断
if (r.contains((int) event.getRawX(), (int) event.getRawY()))
{
return i;
}
}
return -1;
}

这个函数就是依据点击的区域来区分点击到哪个孩子上面的。注意的是取的是上半部分来判定。

然后就是我们的开启动画的代码了。

/**
* 開始动画
*/
private void startAnimation()
{
mIsExchageAnimation = true;
mViewsBottomCache.clear();
mViewsTopCache.clear();
mAnimationStartTime = System.currentTimeMillis(); View view = getChildAt(mTouchIndex);
view.bringToFront(); // 这一句代码是基本的代码 timer = new Timer();
timer.schedule(new TimerTask()
{
@Override
public void run()
{
mAnimationHandler.sendEmptyMessage(0);
}
}, 0, 24);
}

这里的方法也非常简答。初始化一些变量清空缓存。然后开启一个定时任务去发送消息到handler里面事实上这个handler什么事情也没有做,仅仅是不停的在调用requstlayout让他去掉用我们的onLayout方法,最基本的一句代码就是view.bringToFront()这句代码就是会把当前的孩子放在顶层来,事实上就是放在孩子数组里面的最后一个来。这里就是为什么我们要用onlayout去做动画。我们仅仅须要不停的改变onlayout的位置不须要去管ondraw里面假设绘制。事实上底层也是这样绘制的。先绘制前面的孩子,然后在绘制后面。

总结一下这个demo:

1:卡片显示的多少我是直接取的这个控件的一半

2:通过layout来改变动画

3:最重要的就是理解bringtofront里面孩子的排列

4:缓存view的top和bottom

贴上全部代码。凝视都应该非常具体了。

/**
*
* @author edsheng
* @filename ExchageCarldView.java
* @date 2015/3/12
* @version v1.0
*/
public class ExchageCarldView extends ViewGroup
{ private int mTotalHight = 0; // 总高度
private boolean mIsExchageAnimation = false; // 是否在做交换动画
private SparseIntArray mViewsTopCache = new SparseIntArray(); // 卡片顶部边界的cache
private SparseIntArray mViewsBottomCache = new SparseIntArray();// 卡片底部边界的cache private long mAnimationStartTime = 0; // 动画開始的时间
private long Default_animtion_time = 250;// 动画时间
private Timer timer; // 动画定时器
private int mTouchIndex = -1;// touchindex Handler mAnimationHandler = new Handler()
{
public void dispatchMessage(android.os.Message msg)
{
requestLayout(); // 更新界面布局动画
};
}; public ExchageCarldView(Context context)
{
super(context);
} /**
* 缓存view的顶部和底部信息
*
* @param i
* @param view
*/
void cacheViewTopAndBottomIfneed(int i, View view)
{
int viewtop = mViewsTopCache.get(i, -1); if (viewtop == -1)
{
mViewsTopCache.put(i, view.getTop());
} int viewbttom = mViewsBottomCache.get(i, -1);
if (viewbttom == -1)
{
mViewsBottomCache.put(i, view.getBottom());
}
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
measureChildren(widthMeasureSpec, heightMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} /**
* 检測并停止动画
*/
private void checkAnimation()
{
// 当时间到了停止动画
if (Math.abs((System.currentTimeMillis() - mAnimationStartTime)) >= Default_animtion_time)
{
mAnimationHandler.removeMessages(0);
timer.cancel();
mIsExchageAnimation = false;
// postDelayed(new Runnable()
// {
//
// @Override
// public void run()
// {
// requestLayout();
// }
// }, 50);
}
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
int count = getChildCount();
if (mIsExchageAnimation) // 动画路径
{
for (int i = 0; i < count; i++)
{
if (mTouchIndex > i) // 当点击的头部以上的不须要改变layout
continue;
View view = getChildAt(i);
// 缓存第一次view的信息,就是动画刚開始的信息
cacheViewTopAndBottomIfneed(i, view);
if (count - 1 == i) // 最上层的布局
{
// 计算它究竟该走多少高度总高度
int total_dis = view.getHeight() / 2 * (count - 1 - mTouchIndex);
// 计算当前的线性距离
int now_dis = (int) (total_dis * (System.currentTimeMillis() - mAnimationStartTime) / Default_animtion_time);
// 回归不能超过total_dis这个值
int dis = Math.min(now_dis, total_dis); view.layout(view.getLeft(), mViewsTopCache.get(i) + dis, view.getRight(), mViewsBottomCache.get(i) + dis);
}
else
{
// 除去最上层的那个那个布局
// 每一个卡片都应该移动view.height的1/2
int total_dis = view.getHeight() / 2;
// 计算当前的线性距离
int now_dis = (int) (total_dis * (System.currentTimeMillis() - mAnimationStartTime) / Default_animtion_time);
// 回归不能超过total_dis这个值
int dis = Math.min(now_dis, total_dis);
// 放置布局的位置
view.layout(view.getLeft(), mViewsTopCache.get(i) - dis, view.getRight(), mViewsBottomCache.get(i) - dis);
} // 检測动画是否结束
checkAnimation();
}
}
else
{
// 初始化的时候初始化我们的卡片
mTotalHight = 0;
for (int i = 0; i < count; i++)
{
View view = getChildAt(i);
view.layout(getPaddingLeft(), mTotalHight, view.getMeasuredWidth(), mTotalHight + view.getMeasuredHeight());
mTotalHight += view.getMeasuredHeight() / 2; // 这里取的是一半的布局
}
}
} /**
* 開始动画
*/
private void startAnimation()
{
mIsExchageAnimation = true;
mViewsBottomCache.clear();
mViewsTopCache.clear();
mAnimationStartTime = System.currentTimeMillis(); View view = getChildAt(mTouchIndex);
view.bringToFront(); // 这一句代码是基本的代码 timer = new Timer();
timer.schedule(new TimerTask()
{
@Override
public void run()
{
mAnimationHandler.sendEmptyMessage(0);
}
}, 0, 24);
} @Override
public boolean dispatchTouchEvent(MotionEvent event)
{
if (mIsExchageAnimation) // 当有动画的时候我们吃掉这个事件
return true;
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
mTouchIndex = getTouchChildIndex(event); // 获取点击视图的index
if (mTouchIndex != -1 && mTouchIndex != getChildCount() - 1) // 点击的是最后的一个的时候不用开启动画
{
startAnimation();
}
}
// return super.dispatchTouchEvent(event);
// // 仅仅响应最后一个卡片的点击的事件
if (mTouchIndex == getChildCount() - 1)
{
return super.dispatchTouchEvent(event);
}
else
{
// 其它的点击事件吃掉
return true;
}
} /***
* 依据点击的事件获取child的index
*
* @param event
* @return
*/
int getTouchChildIndex(MotionEvent event)
{
for (int i = 0; i < getChildCount(); i++)
{
View view = getChildAt(i);
Rect r = new Rect();
view.getGlobalVisibleRect(r);
r = new Rect(r.left, r.top, r.right, r.bottom - view.getHeight() / 2); // 须要注意的是这里我们是取的上半部分来做点推断
if (r.contains((int) event.getRawX(), (int) event.getRawY()))
{
return i;
}
}
return -1;
}
}

最后是測试代码。

public class CardExchageDemo extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE); ExchageCarldView exchageView = new ExchageCarldView(this); View view = new View(this);
view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 300));
view.setBackgroundColor(Color.YELLOW);
exchageView.addView(view); View view1 = new View(this);
view1.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 300));
view1.setBackgroundColor(Color.BLUE);
exchageView.addView(view1); Button view2 = new Button(this);
view2.setOnClickListener(new OnClickListener()
{ @Override
public void onClick(View v)
{
Toast.makeText(CardExchageDemo.this, "hello", 0).show();
}
});
view2.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 300));
view2.setBackgroundColor(Color.RED);
view2.setText("hello");
exchageView.addView(view2); exchageView.setBackgroundColor(Color.GREEN);
setContentView(exchageView);
}
}

这里开启下载的传送门:点击这里

android自己定义控件系列教程-----仿新版优酷评论剧集卡片滑动控件的更多相关文章

  1. 强烈推荐android初学者,android进阶者看看这个系列教程

    强烈推荐android初学者,android进阶者看看这个系列教程 转载 2015年05月30日 23:05:44 695 为什么要研究Android,是因为它够庞大,它够复杂,他激起了我作为一个程序 ...

  2. android自己定义控件系列教程----视图

    理解android视图 对于android设备我们所示区域事实上和它在底层的绘制有着非常大的关系,非常多时候我们都仅仅关心我们所示,那么在底层一点它究竟是怎么样的一个东西呢?让我们先来看看这个图. w ...

  3. webpack4 系列教程(六): 处理SCSS

    这节课讲解webpack4中处理scss.只需要在处理css的配置上增加编译scss的 LOADER 即可.了解更多处理css的内容 >>> >>> 本节课源码 & ...

  4. sentinel 集群流控原理

    为什么需要集群流控呢?假设需要将某个API的总qps限制在100,机器数可能为50,这时很自然的想到使用一个专门的server来统计总的调用量,其他实例与该server通信来判断是否可以调用,这就是基 ...

  5. Sentinel 发布里程碑版本,添加集群流控功能

    自去年10月底发布GA版本后,Sentinel在近期发布了另一个里程碑版本v1.4(最新的版本号是v1.4.1),加入了开发者关注的集群流控功能. 集群流控简介 为什么要使用集群流控呢?假设我们希望给 ...

  6. Android自己定义控件系列五:自己定义绚丽水波纹效果

    尊重原创!转载请注明出处:http://blog.csdn.net/cyp331203/article/details/41114551 今天我们来利用Android自己定义控件实现一个比較有趣的效果 ...

  7. Android自己定义控件系列案例【五】

    案例效果: 案例分析: 在开发银行相关client的时候或者开发在线支付相关client的时候常常要求用户绑定银行卡,当中银行卡号一般须要空格分隔显示.最常见的就是每4位数以空格进行分隔.以方便用户实 ...

  8. Android控件系列之CheckBox

    学习目的: 1.掌握在Android中如何建立CheckBox 2.掌握CheckBox的常用属性 3.掌握CheckBox选中状态变换的事件(监听器) CheckBox简介: CheckBox和Bu ...

  9. Android控件GridView之仿支付宝钱包首页带有分割线的GridView九宫格的完美实现

    Android控件GridView之仿支付宝钱包首页带有分割线的GridView九宫格的完美实现 2015-03-10 22:38 28419人阅读 评论(17) 收藏 举报  分类: Android ...

随机推荐

  1. 如何创建C# Closure?

    JavaScript中一个重要的概念就是闭包,闭包在JavaScript中有大量的应用,但是你知道么?C#也可以创建Closure.下面就介绍一下如何在C#中创建神奇的闭包. 在这之前,我们必须先知道 ...

  2. java内存组成

     java内存组成介绍:堆(Heap)和非堆(Non-heap)内存 按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配.堆是在 Java 虚拟机启动 ...

  3. 手机中快速看图,浏览编辑DWG 梦想极光CAD

    梦想极光CAD6.0(2016.3.1) 手机版最新更新 1.增加手机上,图纸浏览时预览功能 2.增加直接从手机,QQ接收目录下加载文件功能 3.手机交互界面优化 4.增加新建图纸功能 5.增加缓存功 ...

  4. 13Microsoft SQL Server SQL 高级事务,锁,游标,分区

    Microsoft SQL Server SQL高级事务,锁,游标,分区 通过采用事务和锁机制,解决了数据库系统的并发性问题. 9.1数据库事务 (1)BEGIN TRANSACTION语句定义事务的 ...

  5. 02JavaScript基础语法及数据类型

    JavaScript基础语法及数据类型 2.1数据类型 2.1.1字符串(String) 用单引号或双引号括起来的零个或多个单一的字符所组成. 2.1.2数值(Number) 包含整数或浮点数. 2. ...

  6. 用Java写一个生产者-消费者队列

    生产者消费者的模型作用 通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用. 解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系 ...

  7. jquery的delegate()方法

    delegate() 方法为指定的元素(属于被选元素的子元素)添加一个或多个事件处理程序,并规定当这些事件发生时运行的函数. 使用 delegate() 方法的事件处理程序适用于当前或未来的元素(比如 ...

  8. Extjs选中多行Grid提交

    要实现的效果如图:可以选择多行grid然后提交给后台 1,Extjs中grid如何可以选择多行? 定义一个grid,将色了Type设置为多选即可 selType: 'checkboxmodel', 2 ...

  9. Luogu P3797 妖梦斩木棒

    解题思路 用线段树做这个就不用说了吧,但是要维护的东西确实很神奇.在每一个节点上都维护一个$lbkt$,表示这个区间上最靠左的右括号的位置:一个$rbkt$,表示这个区间上最靠右的左括号的位置.还有一 ...

  10. WIndows 系统下的常用命令 和 检测方法

    ### 一.检测硬盘速度(Windows 自带工具) #### 使用windows 系统自带的工具测试硬盘读写速度 > 在使用下面命令前,需要获得管理员权限,才会在Dos窗口上显示(否则,一闪而 ...