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) 
        
           功能在代码结构图里面也给出了。即:
                     /** 修正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