快乐扑克3走式图: android自定义控件之飞入飞出控件

主页 > 山东快乐扑克3 > 学习经验 > 学Java学Android经验 >

android自定义控件之飞入飞出控件

山东快乐扑克3 www.rn5v7.cn 先看效果图:

 

 

然后看用法代码:

 

 

[java] view plain copy
 
  1. StellarMap stellarMap = (StellarMap) findViewById(R.id.stellar);  
  2.         // 设置数据  
  3.         RecommendAdapter adapter = new RecommendAdapter();  
  4.         stellarMap.setAdapter(adapter);  
  5.   
  6.         // 首页选中  
  7.         stellarMap.setGroup(0true);  
  8.   
  9.         // 拆分屏幕  
  10.         stellarMap.setRegularity(1520);  


 

 

[java] view plain copy
 
  1. class RecommendAdapter implements Adapter {  
  2.         /** 默认组数 */  
  3.         public static final int PAGESIZE    = 15;  
  4.   
  5.         @Override  
  6.         public int getGroupCount() {  
  7.             // 数据分组  
  8.             int groupCount = data.size() / PAGESIZE;  
  9.             // 最后一组  
  10.             if (data.size() % PAGESIZE != 0) {  
  11.                 return groupCount + 1;  
  12.             }  
  13.             return groupCount;  
  14.         }  
  15.   
  16.         @Override  
  17.         public int getCount(int group) {  
  18.             // 最后一组  
  19.             if (data.size() % PAGESIZE != 0) {  
  20.                 if (group == getGroupCount() - 1) {  
  21.                     return data.size() % PAGESIZE;  
  22.                 }  
  23.             }  
  24.             return PAGESIZE;  
  25.         }  
  26.   
  27.         @Override  
  28.         public View getView(int group, int position, View convertView) {  
  29.             TextView tv = new TextView(MainActivity.this);  
  30.             int index = group * PAGESIZE + position;  
  31.             tv.setText(data.get(index));  
  32.             // 随机大小  
  33.             Random random = new Random();  
  34.             // 14-17  
  35.             int size = random.nextInt(4) + 14;  
  36.             tv.setTextSize(size);  
  37.   
  38.             // 随机颜色  
  39.             int alpha = 255;  
  40.             int red = random.nextInt(190) + 30;  
  41.             int green = random.nextInt(190) + 30;  
  42.             int blue = random.nextInt(190) + 30;  
  43.             int argb = Color.argb(alpha, red, green, blue);  
  44.             tv.setTextColor(argb);  
  45.   
  46.             return tv;  
  47.         }  
  48.   
  49.         @Override  
  50.         public int getNextGroupOnPan(int group, float degree) {  
  51.             if(group == getGroupCount() - 1){  
  52.                 group = -1;  
  53.             }  
  54.             return group + 1;  
  55.         }  
  56.   
  57.         @Override  
  58.         public int getNextGroupOnZoom(int group, boolean isZoomIn) {  
  59.             if(group == getGroupCount() - 1){  
  60.                 group = -1;  
  61.             }  
  62.             return group + 1;  
  63.         }  
  64.   
  65.     }  

代码都很简单,我简单说一下,getGroupCount返回一共有多少组,getCount返回一组有多少个元素,getView就不说了,getNextGroupOnPan返回下一个需要放大动画的组数,getNextGroupOnZoom返回下一个需要错小动画的组数。

 

 

接下来才是正餐,我们看看StellarMap的实现,StellarMap继承于FrameLayout:

 

 

[html] view plain copy
 
  1. /** 构造方法 */  
  2.     public StellarMap(Context context, AttributeSet attrs, int defStyle) {  
  3.         super(context, attrs, defStyle);  
  4.         init();  
  5.     }  
  6.   
  7.     public StellarMap(Context context, AttributeSet attrs) {  
  8.         super(context, attrs);  
  9.         init();  
  10.     }  
  11.   
  12.     public StellarMap(Context context) {  
  13.         super(context);  
  14.         init();  
  15.     }  

这个大家应该都很熟,自定义View需要实现的三个构造方法。

 

 

 

[java] view plain copy
 
  1. /** 初始化方法 */  
  2.     private void init() {  
  3.         mGroupCount = 0;  
  4.         mHidenGroupIndex = -1;  
  5.         mShownGroupIndex = -1;  
  6.         mHidenGroup = new RandomLayout(getContext());  
  7.         mShownGroup = new RandomLayout(getContext());  
  8.   
  9.         addView(mHidenGroup, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));  
  10.         mHidenGroup.setVisibility(View.GONE);  
  11.         addView(mShownGroup, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));  
  12.   
  13.         mGestureDetector = new GestureDetector(this);  
  14.         setOnTouchListener(this);  
  15.         // 设置动画  
  16.         mZoomInNearAnim = AnimationUtil.createZoomInNearAnim();  
  17.         mZoomInNearAnim.setAnimationListener(this);  
  18.         mZoomInAwayAnim = AnimationUtil.createZoomInAwayAnim();  
  19.         mZoomInAwayAnim.setAnimationListener(this);  
  20.         mZoomOutNearAnim = AnimationUtil.createZoomOutNearAnim();  
  21.         mZoomOutNearAnim.setAnimationListener(this);  
  22.         mZoomOutAwayAnim = AnimationUtil.createZoomOutAwayAnim();  
  23.         mZoomOutAwayAnim.setAnimationListener(this);  
  24.     }  

代码很清晰,简单说一下,mGroupCount是组数,mHidenGroupIndex是隐藏的组数角标,mShownGroupIndex是显示的组数角标,另外创建了两个RandomLayout,它继承于ViewGroup,用于实现View的随机放入,之后创建手势监听和触摸监听,下面就是四个不同的动画。

 

 

按照代码执行顺序来,下一步是设置Adapter:

 

 

[java] view plain copy
 
  1. /** 设置本Adapter */  
  2.     public void setAdapter(Adapter adapter) {  
  3.         mAdapter = adapter;  
  4.         mGroupCount = mAdapter.getGroupCount();  
  5.         if (mGroupCount > 0) {  
  6.             mShownGroupIndex = 0;  
  7.         }  
  8.         setChildAdapter();  
  9.     }  

可见这里初始化了组数,并调用了setChildAdapter方法:

 

 

 

[java] view plain copy
 
  1. /** 为子Group设置Adapter */  
  2.     private void setChildAdapter() {  
  3.         if (null == mAdapter) {  
  4.             return;  
  5.         }  
  6.         mHidenGroupAdapter = new RandomLayout.Adapter() {  
  7.             // 取出本Adapter的View对象给HidenGroup的Adapter  
  8.             @Override  
  9.             public View getView(int position, View convertView) {  
  10.                 return mAdapter.getView(mHidenGroupIndex, position, convertView);  
  11.             }  
  12.   
  13.             @Override  
  14.             public int getCount() {  
  15.                 return mAdapter.getCount(mHidenGroupIndex);  
  16.             }  
  17.         };  
  18.         mHidenGroup.setAdapter(mHidenGroupAdapter);  
  19.   
  20.         mShownGroupAdapter = new RandomLayout.Adapter() {  
  21.             // 取出本Adapter的View对象给ShownGroup的Adapter  
  22.             @Override  
  23.             public View getView(int position, View convertView) {  
  24.                 return mAdapter.getView(mShownGroupIndex, position, convertView);  
  25.             }  
  26.   
  27.             @Override  
  28.             public int getCount() {  
  29.                 return mAdapter.getCount(mShownGroupIndex);  
  30.             }  
  31.         };  
  32.         mShownGroup.setAdapter(mShownGroupAdapter);  
  33.     }  

该方法为子视图创建Adapter,也就是RandomLayout,我们看看它的实现:

 

 

 

[java] view plain copy
 
  1. /** 构造方法 */  
  2.     public RandomLayout(Context context) {  
  3.         super(context);  
  4.         init();  
  5.     }  

[java] view plain copy
 
  1. /** 初始化方法 */  
  2.     private void init() {  
  3.         mLayouted = false;  
  4.         mRdm = new Random();  
  5.         setRegularity(11);  
  6.         mFixedViews = new HashSet<View>();  
  7.         mRecycledViews = new LinkedList<View>();  
  8.     }  

在init方法中,mLayouted表示该视图是否已经onlayout,mFixedViews存放已经确定位置的View ,mRecycledViews记录被回收的View,以便重复利用,setRegularity(1, 1)方法仅仅只是初始化,会被重新调用,我们后面讲,setAdapter方法就相当简单了:

 

 

 

[java] view plain copy
 
  1. /** 设置数据源 */  
  2.     public void setAdapter(Adapter adapter) {  
  3.         this.mAdapter = adapter;  
  4.     }  

再回到使用代码上,下一句是stellarMap.setGroup(0, true),我们看看实现:

 

 

 

[java] view plain copy
 
  1. /** 给指定的Group设置动画 */  
  2.     public void setGroup(int groupIndex, boolean playAnimation) {  
  3.         switchGroup(groupIndex, playAnimation, mZoomInNearAnim, mZoomInAwayAnim);  
  4.     }  

[java] view plain copy
 
  1. /** 给下一个Group设置进出动画 */  
  2.     private void switchGroup(int newGroupIndex, boolean playAnimation, Animation inAnim,  
  3.             Animation outAnim) {  
  4.         if (newGroupIndex < 0 || newGroupIndex >= mGroupCount) {  
  5.             return;  
  6.         }  
  7.         // 把当前显示Group角标设置为隐藏的  
  8.         mHidenGroupIndex = mShownGroupIndex;  
  9.         // 把下一个Group角标设置为显示的  
  10.         mShownGroupIndex = newGroupIndex;  
  11.         // 交换两个Group  
  12.         RandomLayout temp = mShownGroup;  
  13.         mShownGroup = mHidenGroup;  
  14.         mShownGroup.setAdapter(mShownGroupAdapter);  
  15.         mHidenGroup = temp;  
  16.         mHidenGroup.setAdapter(mHidenGroupAdapter);  
  17.         // 刷新显示的Group  
  18.         mShownGroup.refresh();  
  19.         // 显示Group  
  20.         mShownGroup.setVisibility(View.VISIBLE);  
  21.   
  22.         // 启动动画  
  23.         if (playAnimation) {  
  24.             if (mShownGroup.hasLayouted()) {  
  25.                 mShownGroup.startAnimation(inAnim);  
  26.             }  
  27.             mHidenGroup.startAnimation(outAnim);  
  28.         } else {  
  29.             mHidenGroup.setVisibility(View.GONE);  
  30.         }  
  31.     }  

switchGroup方法正是StellarMap的核心方法,通过交换show和hide的角标与adapter,完成显示和隐藏的切换,并开启过度动画。

 

 

最后一行代码,stellarMap.setRegularity(15, 20)方法:

 

 

[java] view plain copy
 
  1. /** 设置隐藏组和显示组的x和y的规则 */  
  2.     public void setRegularity(int xRegularity, int yRegularity) {  
  3.         mHidenGroup.setRegularity(xRegularity, yRegularity);  
  4.         mShownGroup.setRegularity(xRegularity, yRegularity);  
  5.     }  

用于设置屏幕的分割,再看RandomLayout的setRegularity方法:

 

 

 

[java] view plain copy
 
  1. /** 设置mXRegularity和mXRegularity,确定区域的个数 */  
  2.     public void setRegularity(int xRegularity, int yRegularity) {  
  3.         if (xRegularity > 1) {  
  4.             this.mXRegularity = xRegularity;  
  5.         } else {  
  6.             this.mXRegularity = 1;  
  7.         }  
  8.         if (yRegularity > 1) {  
  9.             this.mYRegularity = yRegularity;  
  10.         } else {  
  11.             this.mYRegularity = 1;  
  12.         }  
  13.         this.mAreaCount = mXRegularity * mYRegularity;// 个数等于x方向的个数*y方向的个数  
  14.         this.mAreaDensity = new int[mYRegularity][mXRegularity];// 存放区域的二维数组  
  15.     }  

这里保存了屏幕被分割的快数,并创建了一个二维数组,定位具体的位置,它的onLayout便是区域分布的关键:

 

 

 

[java] view plain copy
 
  1. /** 确定子View的位置,这个就是区域分布的关键 */  
  2.     @Override  
  3.     public void onLayout(boolean changed, int l, int t, int r, int b) {  
  4.         final int count = getChildCount();  
  5.         // 确定自身的宽高  
  6.         int thisW = r - l - this.getPaddingLeft() - this.getPaddingRight();  
  7.         int thisH = b - t - this.getPaddingTop() - this.getPaddingBottom();  
  8.         // 自身内容区域的右边和下边  
  9.         int contentRight = r - getPaddingRight();  
  10.         int contentBottom = b - getPaddingBottom();  
  11.         // 按照顺序存放把区域存放到集合中  
  12.         List<Integer> availAreas = new ArrayList<Integer>(mAreaCount);  
  13.         for (int i = 0; i < mAreaCount; i++) {  
  14.             availAreas.add(i);  
  15.         }  
  16.   
  17.         int areaCapacity = (count + 1) / mAreaCount + 1// 区域密度,表示一个区域内可以放几个View,+1表示至少要放一个  
  18.         int availAreaCount = mAreaCount; // 可用的区域个数  
  19.   
  20.         for (int i = 0; i < count; i++) {  
  21.             final View child = getChildAt(i);  
  22.             if (child.getVisibility() == View.GONE) { // gone掉的view是不参与布局  
  23.                 continue;  
  24.             }  
  25.   
  26.             if (!mFixedViews.contains(child)) {// mFixedViews用于存放已经确定好位置的View,存到了就没必要再次存放  
  27.                 LayoutParams params = (LayoutParams) child.getLayoutParams();  
  28.                 // 先测量子View的大小  
  29.                 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(this.getMeasuredWidth(), MeasureSpec.AT_MOST);// 为子View准备测量的参数  
  30.                 int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(this.getMeasuredHeight(), MeasureSpec.AT_MOST);  
  31.                 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  32.                 // 子View测量之后的宽和高  
  33.                 int childW = child.getMeasuredWidth();  
  34.                 int childH = child.getMeasuredHeight();  
  35.                 // 用自身的高度去除以分配值,可以算出每一个区域的宽和高  
  36.                 float colW = thisW / (float) mXRegularity;  
  37.                 float rowH = thisH / (float) mYRegularity;  
  38.   
  39.                 while (availAreaCount > 0) { // 如果使用区域大于0,就可以为子View尝试分配  
  40.                     int arrayIdx = mRdm.nextInt(availAreaCount);// 随机一个list中的位置  
  41.                     int areaIdx = availAreas.get(arrayIdx);// 再根据list中的位置获取一个区域编号  
  42.                     int col = areaIdx % mXRegularity;// 计算出在二维数组中的位置  
  43.                     int row = areaIdx / mXRegularity;  
  44.                     if (mAreaDensity[row][col] < areaCapacity) {// 区域密度未超过限定,将view置入该区域  
  45.                         int xOffset = (int) colW - childW; // 区域宽度 和 子View的宽度差值,差值可以用来做区域内的位置随机  
  46.                         if (xOffset <= 0) {// 说明子View的宽比较大  
  47.                             xOffset = 1;  
  48.                         }  
  49.                         int yOffset = (int) rowH - childH;  
  50.                         if (yOffset <= 0) {// 说明子View的高比较大  
  51.                             yOffset = 1;  
  52.                         }  
  53.                         // 确定左边,等于区域宽度*左边的区域  
  54.                         params.mLeft = getPaddingLeft() + (int) (colW * col + mRdm.nextInt(xOffset));  
  55.                         int rightEdge = contentRight - childW;  
  56.                         if (params.mLeft > rightEdge) {// 加上子View的宽度后不能超出右边界  
  57.                             params.mLeft = rightEdge;  
  58.                         }  
  59.                         params.mRight = params.mLeft + childW;  
  60.   
  61.                         params.mTop = getPaddingTop() + (int) (rowH * row + mRdm.nextInt(yOffset));  
  62.                         int bottomEdge = contentBottom - childH;  
  63.                         if (params.mTop > bottomEdge) {// 加上子View的宽度后不能超出右边界  
  64.                             params.mTop = bottomEdge;  
  65.                         }  
  66.                         params.mBottom = params.mTop + childH;  
  67.   
  68.                         if (!isOverlap(params)) {// 判断是否和别的View重叠了  
  69.                             mAreaDensity[row][col]++;// 没有重叠,把该区域的密度加1  
  70.                             child.layout(params.mLeft, params.mTop, params.mRight, params.mBottom);// 布局子View  
  71.                             mFixedViews.add(child);// 添加到已经布局的集合中  
  72.                             break;  
  73.                         } else {// 如果重叠了,把该区域移除,  
  74.                             availAreas.remove(arrayIdx);  
  75.                             availAreaCount--;  
  76.                         }  
  77.                     } else {// 区域密度超过限定,将该区域从可选区域中移除  
  78.                         availAreas.remove(arrayIdx);  
  79.                         availAreaCount--;  
  80.                     }  
  81.                 }  
  82.             }  
  83.         }  
  84.         mLayouted = true;  
  85.     }  

说实在的,这么长的代码分析起来着实有点费劲,必要的部分我加了注释,这里就不多说了。

 

 

在StellarMap中加入了手势,用于用户滑动的时候给与交互:

 

 

[java] view plain copy
 
  1. @Override  
  2.     public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {  
  3.         int centerX = getMeasuredWidth() / 2;  
  4.         int centerY = getMeasuredWidth() / 2;  
  5.   
  6.         int x1 = (int) e1.getX() - centerX;  
  7.         int y1 = (int) e1.getY() - centerY;  
  8.         int x2 = (int) e2.getX() - centerX;  
  9.         int y2 = (int) e2.getY() - centerY;  
  10.   
  11.         if ((x1 * x1 + y1 * y1) > (x2 * x2 + y2 * y2)) {  
  12.             zoomOut();  
  13.         } else {  
  14.             zoomIn();  
  15.         }  
  16.         return true;  
  17.     }  

[java] view plain copy
 
  1. /** 给Group设置动画入 */  
  2.     public void zoomIn() {  
  3.         final int nextGroupIndex = mAdapter.getNextGroupOnZoom(mShownGroupIndex, true);  
  4.         switchGroup(nextGroupIndex, true, mZoomInNearAnim, mZoomInAwayAnim);  
  5.     }  
  6.   
  7.     /** 给Group设置出动画 */  
  8.     public void zoomOut() {  
  9.         final int nextGroupIndex = mAdapter.getNextGroupOnZoom(mShownGroupIndex, false);  
  10.         switchGroup(nextGroupIndex, true, mZoomOutNearAnim, mZoomOutAwayAnim);  
  11.     }  

可见最后还是调回了我们的switchGroup方法。

 

 


上一篇: android xutil 使用详解   下一篇: Android —— 内存泄漏检查
28| 418| 792| 606| 242| 414| 572| 659| 283| 818|