Android自定义搜索时的关键字气泡动画-原理详解
来源:互联网 发布:阿里云域名添加解析 编辑:程序博客网 时间:2024/06/04 19:12
Android自定义搜索时的关键字气泡动画-原理详解
在开发的过程中,很多都满足不了需求,所以自定义View是一个很有必要的一步。
这篇文章会分析一个 自定义搜索时的关键字气泡动画 的一个自定义View
下面先粗略的介绍下自定义View的步骤.
1 良好的自定义View
易用,标准,开放。
一个设计良好的自定义view和其他设计良好的类很像。封装了某个具有易用性接口的功能组合,这些功能能够有效地使用CPU和内存,并且十分开放的。但是,除了开始一个设计良好的类之外,一个自定义view应该:
l 符合安卓标准
l 提供能够在Android XML布局中工作的自定义样式属性
l 发送可访问的事件
l 与多个Android平台兼容。
2 创建自定义View (步骤)
2.1 继承View完全自定义或继承View的派生子类(控件或布局都为View的派生类)
2.2 定义自定义属性
也就是 在资源元素<declare-styleable>中为您的view定义自定义属性
2.3 获取自定义属性
当View在XML中被创建时 如果有给自定义的属性 赋上值 则可以在Class文件中获取属性的内容
2.4 添加属性和事件
定义需要的逻辑需要的属性。
自定义view同样需要支持和重要事件交流的事件监听器。
各种 点击、触摸等一系列的事件
2.5 对控件的测量
一般在onMesure()中实现,对控件的 宽高属性 进行测量。
2.6 自定义绘制(实施)
一般有继承控件的都要实现 重写onDraw()方法. onDraw()的参数是视图可以用来绘制自己的Canvas对象,你可以在onDraw() 里使用这些方法创建你的自定义用户界面(UI).
l 画什么, 由Canvas处理
l 怎么画, 由Paint处理
3 优化
包括各方面的优化。这里不做多提示
----------------------------------------------------------------------------------------------------------------------
以上是简单的效果图,但是可以修改可以放到很多应用中使用。
按钮In Out 的作用分别是,将这些关键字文本 向内消失 Out 反之相反。
整体的就两个.JAVA 文件。 其中KeywordsFlow 为一个继承FragmentLayouit自定义View
先来看下MainActivity 如何调用 View
public class MainActivity extends Activity implements OnClickListener{ public static final String[] keywords = { "QQ", "BaseAnimation", "APK", "GFW", "铅笔", // "短信", "桌面精灵", "MacBook Pro", "平板电脑", "雅诗兰黛", // "Base", "笔记本", "SPY Mouse", "Thinkpad E40", "捕鱼达人", // "内存清理", "地图", "导航", "闹钟", "主题", // "通讯录", "播放器", "CSDN leak", "安全", "Animation", // "美女", "天气", "4743G", "戴尔", "联想", // "欧朋", "浏览器", "愤怒的小鸟", "mmShow", "网易公开课", // "iciba", "油水关系", "网游App", "互联网", "365日历", // "脸部识别", "Chrome", "Safari", "中国版Siri", "苹果", // "iPhone5S", "摩托 ME525", "魅族 MX3", "小米" }; private Button btn1, btn2; private KeywordsFlow keywordsFlow; @Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initViews(); } private void initViews() {btn1 = (Button) findViewById(R.id.button1);btn2 = (Button) findViewById(R.id.button2);btn1.setOnClickListener(this);btn2.setOnClickListener(this);keywordsFlow = (KeywordsFlow) findViewById(R.id.frameLayout1);keywordsFlow.setDuration(2000l);keywordsFlow.setOnItemClickListener(this); } private static void feedKeywordsFlow(KeywordsFlow keywordsFlow, String[] arr) {Random random = new Random();// 获取10个数据for (int i = 0; i < KeywordsFlow.MAX; i++){ int ran = random.nextInt(arr.length); String tmp = arr[ran]; keywordsFlow.feedKeyword(tmp);} } @Override public void onClick(View v) {switch (v.getId()){case R.id.button1: keywordsFlow.rubKeywords(); feedKeywordsFlow(keywordsFlow, keywords); keywordsFlow.go2Show(KeywordsFlow.ANIMATION_IN); break;case R.id.button2: keywordsFlow.rubKeywords(); feedKeywordsFlow(keywordsFlow, keywords); keywordsFlow.go2Show(KeywordsFlow.ANIMATION_OUT); break;} }}
代码很简单。就两个按钮 一个View 定义了数据集。feedKeywordsFlow()中,调用
keywordsFlow.feedKeyword(tmp); 传输需要显示的文本。
switch (v.getId()){case R.id.button1: keywordsFlow.rubKeywords(); feedKeywordsFlow(keywordsFlow, keywords); keywordsFlow.go2Show(KeywordsFlow.ANIMATION_IN); break;case R.id.button2: keywordsFlow.rubKeywords(); feedKeywordsFlow(keywordsFlow, keywords); keywordsFlow.go2Show(KeywordsFlow.ANIMATION_OUT); break;}在编写这个自定View中 这几个调用的方法占有重要的比例。
首先先来看下整体的 代码结构图:
整个方法调用都围绕着这个图进行的。 接下来会一个模块一个方法进行分析。
首先按照结构图 第一步需要调用 keywordsFlow.rubKeywords();
/** 存储显示的关键字。 */private Vector<String> vecKeywords;public void rubKeywords() {vecKeywords.clear();}
在开始传入关键字时,先清除掉 存有上一次显示的关键字的容器。
在容器的关键字清空后,我们就可以往容器里面添加 关键字了。
Random random = new Random();// 获取10个数据for (int i = 0; i < KeywordsFlow.MAX; i++){ int ran = random.nextInt(arr.length); String tmp = arr[ran]; keywordsFlow.feedKeyword(tmp);}
public boolean feedKeyword(String keyword) {boolean result = false;if (vecKeywords.size() < MAX) {result = vecKeywords.add(keyword);}return result;}
这2段添加的代码也很简单 就是往容器添加数据。 但是容器最多能容纳MAX个 这个可以自行修改。
在数据方面的初始化完成,接下来就是 很关键的
Boolean go2Show(int animType)
先来看下对这个方法的描述
/** * 开始动画显示。 * 之前已经存在的TextView将会显示退出动画,最后再调用show()来显示下一波的关键字。 * * @return 正常显示动画返回true;反之为false。返回false原因如下: * 1.时间上不允许,受lastStartAnimationTime的制约; * 2.未获取到width和height的值。 */
接下来看一下 代码:
上面的代码 我用笔记标出来 很详细了。用if 判断两次点击的按钮的时间差,防止在动画没显完 又来一波动画 多疑控制时间差。
再来分析下下几个 动画类型和模式的 常量
/** 由外至内的动画。 */public static final int ANIMATION_IN = 1;/** 由内至外的动画。 */public static final int ANIMATION_OUT = 2;/** 位移动画类型:从外围移动到坐标点。 */public static final int OUTSIDE_TO_LOCATION = 1;/** 位移动画类型:从坐标点移动到外围。 */public static final int LOCATION_TO_OUTSIDE = 2;/** 位移动画类型:从中心点移动到坐标点。 */public static final int CENTER_TO_LOCATION = 3;/** 位移动画类型:从坐标点移动到中心点。 */public static final int LOCATION_TO_CENTER = 4;
从这张图可以很详细的知道 这些常量对应的控件动画类型。(图上之是描述一个关键字文本的动画类型)
根据是那种按钮点击进来的 就传入对应的 动画类型。
设置完了动画类型变量 接下来就到diapper();
在代码结构图也给出了 此方法的作用。 先把上一次的关键字文本 按动画进行退出。
private void disapper() {int size = getChildCount();Log.i("TAG", "当前视图 孩子视图 有:" + size);for (int i = size - 1; i >= 0; i--) {final TextView txt = (TextView) getChildAt(i);if (txt.getVisibility() == View.GONE) {removeView(txt);continue;}FrameLayout.LayoutParams layParams = (LayoutParams) txt.getLayoutParams();Log.i("ANDROID_LAB", txt.getText() + " leftM=" + layParams.leftMargin + " topM=" + layParams.topMargin + " width=" + txt.getWidth());int[] xy = new int[] { layParams.leftMargin, layParams.topMargin, txt.getWidth() };AnimationSet animSet = getAnimationSet(xy, (width >> 1), (height >> 1), txtAnimOutType);txt.startAnimation(animSet);animSet.setAnimationListener(new AnimationListener() {public void onAnimationStart(Animation animation) {}public void onAnimationRepeat(Animation animation) {}public void onAnimationEnd(Animation animation) {txt.setOnClickListener(null);txt.setClickable(false);txt.setVisibility(View.GONE);}});}}
因为动画显示完后的 时间里:
public void onAnimationEnd(Animation animation) {txt.setOnClickListener(null);txt.setClickable(false);txt.setVisibility(View.GONE);}
会将关键字 GONE 所以在下一次调用的时候 将为gone的关键字文本 从容器中一处。
最后AnimationSet animSet 为这些 将要退出去的关键字文本 设置退出去的动画.
diapper();调用完后 屏幕上是没有任何 关键字文本的。 则我们要让新的一批 关键字文本
加入容器中 显示出来。 那就是show();
private boolean show() {if (width > 0 && height > 0 && vecKeywords != null && vecKeywords.size() > 0 && enableShow) {enableShow = false;lastStartAnimationTime = System.currentTimeMillis();// 找到中心点int xCenter = width >> 1, yCenter = height >> 1;// 关键字的个数。int size = vecKeywords.size();int xItem = width / size, yItem = height / size;Log.d("ANDROID_LAB", "--------------------------width=" + width + " height=" + height + " xItem=" + xItem + " yItem=" + yItem + "---------------------------");LinkedList<Integer> listX = new LinkedList<Integer>(), listY = new LinkedList<Integer>();for (int i = 0; i < size; i++) {// 准备随机候选数,分别对应x/y轴位置listX.add(i * xItem);listY.add(i * yItem + (yItem >> 2));Log.e("Search", "ListX:" + (i * xItem) + "#listY:" + (i * yItem + (yItem >> 2)));}// TextView[] txtArr = new TextView[size];LinkedList<TextView> listTxtTop = new LinkedList<TextView>();LinkedList<TextView> listTxtBottom = new LinkedList<TextView>();for (int i = 0; i < size; i++) {String keyword = vecKeywords.get(i);// 随机颜色int ranColor = 0xff000000 | random.nextInt(0x0077ffff);// 随机位置,糙值int xy[] = randomXY(random, listX, listY, xItem);// 随机字体大小int txtSize = TEXT_SIZE_MIN + random.nextInt(TEXT_SIZE_MAX - TEXT_SIZE_MIN + 1);// 实例化TextViewfinal TextView txt = new TextView(getContext());txt.setOnClickListener(itemClickListener);txt.setText(keyword);txt.setTextColor(ranColor);txt.setTextSize(TypedValue.COMPLEX_UNIT_SP, txtSize);txt.setShadowLayer(1, 1, 1, 0xdd696969);txt.setGravity(Gravity.CENTER);// txt.setBackgroundColor(Color.RED);// 获取文本长度Paint paint = txt.getPaint();int strWidth = (int) Math.ceil(paint.measureText(keyword));xy[IDX_TXT_LENGTH] = strWidth;// 第一次修正:修正x坐标if (xy[IDX_X] + strWidth > width - (xItem >> 1)) {int baseX = width - strWidth;// 减少文本右边缘一样的概率xy[IDX_X] = baseX - xItem + random.nextInt(xItem >> 1);} else if (xy[IDX_X] == 0) {// 减少文本左边缘一样的概率xy[IDX_X] = Math.max(random.nextInt(xItem), xItem / 3);}xy[IDX_DIS_Y] = Math.abs(xy[IDX_Y] - yCenter);txt.setTag(xy);if (xy[IDX_Y] > yCenter) {listTxtBottom.add(txt);} else {listTxtTop.add(txt);}}attach2Screen(listTxtTop, xCenter, yCenter, yItem);attach2Screen(listTxtBottom, xCenter, yCenter, yItem);return true;}return false;}
在代码中已经 标出一些 重要的关键语句 以及 语句的重要性。
因为在显示的关键字文本 可能出现与边缘重叠的情况,则为这个会发现与边缘重叠的情况 做了修正。
最后一个if 语句就是为了修正 位置。
也给出了 修正边缘图的的原理。仔细分析还是很容易参透。
在show的最后 还有一个
/** 修正TextView的Y坐标将将其添加到容器上。 */
private void attach2Screen(LinkedList<TextView> listTxt, int xCenter, int yCenter, int yItem)
private void attach2Screen(LinkedList<TextView> listTxt, int xCenter, int yCenter, int yItem)
功能在代码结构图里面也给出了。即:
/** 修正TextView的Y坐标将将其添加到容器上。 */
这个是第二次修正,第一次修正与边缘的距离 这一次修正是为了 两两关键字文本不相交。
private void attach2Screen(LinkedList<TextView> listTxt, int xCenter, int yCenter, int yItem) {int size = listTxt.size(); sortXYList(listTxt, size);for (int i = 0; i < size; i++) {TextView txt = listTxt.get(i);int[] iXY = (int[]) txt.getTag();// 第二次修正:修正y坐标int yDistance = iXY[IDX_Y] - yCenter;// 对于最靠近中心点的,其值不会大于yItem// 对于可以一路下降到中心点的,则该值也是其应调整的大小int yMove = Math.abs(yDistance);inner: for (int k = i - 1; k >= 0; k--) {int[] kXY = (int[]) listTxt.get(k).getTag();int startX = kXY[IDX_X];int endX = startX + kXY[IDX_TXT_LENGTH];// y轴以中心点为分隔线,在同一侧if (yDistance * (kXY[IDX_Y] - yCenter) > 0) {// Log.d("ANDROID_LAB", "compare:" +// listTxt.get(k).getText());if (isXMixed(startX, endX, iXY[IDX_X], iXY[IDX_X] + iXY[IDX_TXT_LENGTH])) {int tmpMove = Math.abs(iXY[IDX_Y] - kXY[IDX_Y]);if (tmpMove > yItem) {yMove = tmpMove;} else if (yMove > 0) {// 取消默认值。yMove = 0;}// Log.d("ANDROID_LAB", "break");break inner;}}}if (yMove > yItem) {int maxMove = yMove - yItem;int randomMove = random.nextInt(maxMove);int realMove = Math.max(randomMove, maxMove >> 1) * yDistance / Math.abs(yDistance);iXY[IDX_Y] = iXY[IDX_Y] - realMove;iXY[IDX_DIS_Y] = Math.abs(iXY[IDX_Y] - yCenter);// 已经调整过前i个需要再次排序sortXYList(listTxt, i + 1);}FrameLayout.LayoutParams layParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);layParams.gravity = Gravity.LEFT | Gravity.TOP;layParams.leftMargin = iXY[IDX_X];layParams.topMargin = iXY[IDX_Y];addView(txt, layParams);// 动画AnimationSet animSet = getAnimationSet(iXY, xCenter, yCenter, txtAnimInType);txt.startAnimation(animSet);}}
接下来最重要的是要注意 isMixed 这个方法 这个是 防止两两有相交的。 做出相对应的调整。
整体的思路已经体现在了代码结构图上。 注重的是 思路。 一些逻辑的操作 可以 有兴趣在深究。
在这个项目上 还可以修改很多 东西。 比如说点击时间。 更多的动画效果等。
整体的动态显示效果
项目代码:
demo
0 0
- Android自定义搜索时的关键字气泡动画-原理详解
- Android 动画原理详解
- Android-搜索关键字飞入飞出动画
- android动画模块 Tween 动画的实现原理 详解
- Android,气泡动画。(碰撞算法的半成品)
- Android自定义View实现搜索动画效果
- android动画详解二 属性动画原理
- Android自定义SeekBar,滑动时弹出气泡指示器显示进度
- Android 自定义activity切换动画实现,overridePendingTransition的使用详解
- android 搜索时关键字变色
- Android自定义view --Path 的高级用法之-搜索按钮动画
- Android 抽奖 转盘 动画 实现原理详解
- ios气泡动画效果的简单实现
- Android 方便快捷简单自定义提示气泡
- Android 环信 自定义聊天气泡
- Android自定义view案例一气泡框
- android简单的自定义动画
- Android动画的实现原理
- WWDC2016 Session笔记 - Xcode 8 Auto Layout新特性
- 多态
- Linux 文件名搜索 whereis,locate,find
- 关于监听(安卓)
- 基本赋值运算符和扩展赋值运算符的区别
- Android自定义搜索时的关键字气泡动画-原理详解
- Java Collections与Arrays的浅解
- angular select2()
- Android 对对话框进行监听
- 简单单选、多选按钮设计及监听
- 【poj3241】 Object Clustering
- 用bootstrap做下拉菜单,并使用jQuery实现从服务器加载下拉菜单的item。
- 初等数论笔记
- 演示 pull解析的基本步骤(代码演示)