android自己定义控件系列教程-----仿新版优酷评论剧集卡片滑动控件
我们先来看看优酷的控件是怎么回事?
仅仅响应最后也就是最顶部的卡片的点击事件,假设点击的不是最顶部的卡片那么就先把它放到最顶部。然后在移动到最前面来。重复如次。
知道了这几条那么我们就非常好做了。
里面的技术细节可能就是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自己定义控件系列教程-----仿新版优酷评论剧集卡片滑动控件的更多相关文章
- 强烈推荐android初学者,android进阶者看看这个系列教程
强烈推荐android初学者,android进阶者看看这个系列教程 转载 2015年05月30日 23:05:44 695 为什么要研究Android,是因为它够庞大,它够复杂,他激起了我作为一个程序 ...
- android自己定义控件系列教程----视图
理解android视图 对于android设备我们所示区域事实上和它在底层的绘制有着非常大的关系,非常多时候我们都仅仅关心我们所示,那么在底层一点它究竟是怎么样的一个东西呢?让我们先来看看这个图. w ...
- webpack4 系列教程(六): 处理SCSS
这节课讲解webpack4中处理scss.只需要在处理css的配置上增加编译scss的 LOADER 即可.了解更多处理css的内容 >>> >>> 本节课源码 & ...
- sentinel 集群流控原理
为什么需要集群流控呢?假设需要将某个API的总qps限制在100,机器数可能为50,这时很自然的想到使用一个专门的server来统计总的调用量,其他实例与该server通信来判断是否可以调用,这就是基 ...
- Sentinel 发布里程碑版本,添加集群流控功能
自去年10月底发布GA版本后,Sentinel在近期发布了另一个里程碑版本v1.4(最新的版本号是v1.4.1),加入了开发者关注的集群流控功能. 集群流控简介 为什么要使用集群流控呢?假设我们希望给 ...
- Android自己定义控件系列五:自己定义绚丽水波纹效果
尊重原创!转载请注明出处:http://blog.csdn.net/cyp331203/article/details/41114551 今天我们来利用Android自己定义控件实现一个比較有趣的效果 ...
- Android自己定义控件系列案例【五】
案例效果: 案例分析: 在开发银行相关client的时候或者开发在线支付相关client的时候常常要求用户绑定银行卡,当中银行卡号一般须要空格分隔显示.最常见的就是每4位数以空格进行分隔.以方便用户实 ...
- Android控件系列之CheckBox
学习目的: 1.掌握在Android中如何建立CheckBox 2.掌握CheckBox的常用属性 3.掌握CheckBox选中状态变换的事件(监听器) CheckBox简介: CheckBox和Bu ...
- Android控件GridView之仿支付宝钱包首页带有分割线的GridView九宫格的完美实现
Android控件GridView之仿支付宝钱包首页带有分割线的GridView九宫格的完美实现 2015-03-10 22:38 28419人阅读 评论(17) 收藏 举报 分类: Android ...
随机推荐
- 我的DBDA类
<?php class DBDA { public $host="localhost"; public $uid="root"; public $pwd= ...
- JNDI链接SQLServer数据库步骤
1.配置context.xml文件 在我们的WebRoot目录下,就是和WEB-INF同级的目录下,新建一个META-INF的目录(假如不存在),在该目录下创建一个context.xml文件,并且在c ...
- BackboneJS and UnderscoreJS
介绍 来自API(backbone能做什么?) 当我们开发含有大量Javascript的web应用程序时,首先你需要做的事情之一便是停止向DOM对象附加数据. 通过复杂多变的jQuery选择符和回调 ...
- ThinkPHP---thinkphp框架介绍
目录: (1)简述: (2)下载: (3)文件结构: (4)部署: (5)细节问题: 主体: (1)简述 ThinkPHP诞生于2006年初,最初叫FSC.于2007年元旦更名为PHP,同时官网上线. ...
- Java 数组中寻找最大子数组
程序设计思想: 依次将数组划分开,先判断一个元素的单个数组大小,接下来两个,依次上升,最后将所得结果进行比较赋值,输出最大结果. 1 package ketangTest; //张生辉,康治家 201 ...
- python爬虫21 | 对于b站这样的滑动验证码,不好意思,照样自动识别
今天 要来说说滑动验证码了 大家应该都很熟悉 点击滑块然后移动到图片缺口进行验证 现在越来越多的网站使用这样的验证方式 为的是增加验证码识别的难度 那么 对于这种验证码 应该怎么破呢 接下来就是 学习 ...
- LINUX-系统信息
系统信息 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SMBIOS ...
- gnulpot
gnulpot Table of Contents 1. Label position 2. coordinates 3. Symbols 4. key 4.1. key position 4.2. ...
- vuex----------state的基础用法
先使用vue cli构建一个自己的vue项目 1.npm i -g vue-cli 2.vue init webpack sell (sell是你的项目名) 3.一路回车(在这个过程中会提示你是否安装 ...
- Python基础之生成器、迭代器
一.字符串格式化进阶 Python的字符串格式化有两种方式: 百分号方式.format方式,由于百分号的方式相对来说比较老,在社区里讨论format方式有望取代百分号方式,下面我们分别介绍一下这两种方 ...