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


      近期呢,本人辞职了。在找工作期间。不幸碰到了这个求职淡季,另外还是大学生毕业求职的高峰期,简历发了无数份却都石沉大海。宝宝心里那是一个苦啊!

    翻着过去的代码,本人偶然找到了一个有意思的控件。那时本人还没有写博客的习惯,如今补上,先看效果图:




    然后看使用方法代码:


    StellarMap stellarMap = (StellarMap) findViewById(R.id.stellar);
    		// 设置数据
    		RecommendAdapter adapter = new RecommendAdapter();
    		stellarMap.setAdapter(adapter);
    
    		// 首页选中
    		stellarMap.setGroup(0, true);
    
    		// 拆分屏幕
    		stellarMap.setRegularity(15, 20);


    class RecommendAdapter implements Adapter {
    		/** 默认组数 */
    		public static final int	PAGESIZE	= 15;
    
    		@Override
    		public int getGroupCount() {
    			// 数据分组
    			int groupCount = data.size() / PAGESIZE;
    			// 最后一组
    			if (data.size() % PAGESIZE != 0) {
    				return groupCount + 1;
    			}
    			return groupCount;
    		}
    
    		@Override
    		public int getCount(int group) {
    			// 最后一组
    			if (data.size() % PAGESIZE != 0) {
    				if (group == getGroupCount() - 1) {
    					return data.size() % PAGESIZE;
    				}
    			}
    			return PAGESIZE;
    		}
    
    		@Override
    		public View getView(int group, int position, View convertView) {
    			TextView tv = new TextView(MainActivity.this);
    			int index = group * PAGESIZE + position;
    			tv.setText(data.get(index));
    			// 随机大小
    			Random random = new Random();
    			// 14-17
    			int size = random.nextInt(4) + 14;
    			tv.setTextSize(size);
    
    			// 随机颜色
    			int alpha = 255;
    			int red = random.nextInt(190) + 30;
    			int green = random.nextInt(190) + 30;
    			int blue = random.nextInt(190) + 30;
    			int argb = Color.argb(alpha, red, green, blue);
    			tv.setTextColor(argb);
    
    			return tv;
    		}
    
    		@Override
    		public int getNextGroupOnPan(int group, float degree) {
    			if(group == getGroupCount() - 1){
    				group = -1;
    			}
    			return group + 1;
    		}
    
    		@Override
    		public int getNextGroupOnZoom(int group, boolean isZoomIn) {
    			if(group == getGroupCount() - 1){
    				group = -1;
    			}
    			return group + 1;
    		}
    
    	}

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


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


    /** 构造方法 */
    	public StellarMap(Context context, AttributeSet attrs, int defStyle) {
    		super(context, attrs, defStyle);
    		init();
    	}
    
    	public StellarMap(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		init();
    	}
    
    	public StellarMap(Context context) {
    		super(context);
    		init();
    	}

    这个大家应该都非常熟,自己定义View须要实现的三个构造方法。


    /** 初始化方法 */
    	private void init() {
    		mGroupCount = 0;
    		mHidenGroupIndex = -1;
    		mShownGroupIndex = -1;
    		mHidenGroup = new RandomLayout(getContext());
    		mShownGroup = new RandomLayout(getContext());
    
    		addView(mHidenGroup, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
    		mHidenGroup.setVisibility(View.GONE);
    		addView(mShownGroup, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
    
    		mGestureDetector = new GestureDetector(this);
    		setOnTouchListener(this);
    		// 设置动画
    		mZoomInNearAnim = AnimationUtil.createZoomInNearAnim();
    		mZoomInNearAnim.setAnimationListener(this);
    		mZoomInAwayAnim = AnimationUtil.createZoomInAwayAnim();
    		mZoomInAwayAnim.setAnimationListener(this);
    		mZoomOutNearAnim = AnimationUtil.createZoomOutNearAnim();
    		mZoomOutNearAnim.setAnimationListener(this);
    		mZoomOutAwayAnim = AnimationUtil.createZoomOutAwayAnim();
    		mZoomOutAwayAnim.setAnimationListener(this);
    	}

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


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


    /** 设置本Adapter */
    	public void setAdapter(Adapter adapter) {
    		mAdapter = adapter;
    		mGroupCount = mAdapter.getGroupCount();
    		if (mGroupCount > 0) {
    			mShownGroupIndex = 0;
    		}
    		setChildAdapter();
    	}

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


    /** 为子Group设置Adapter */
    	private void setChildAdapter() {
    		if (null == mAdapter) {
    			return;
    		}
    		mHidenGroupAdapter = new RandomLayout.Adapter() {
    			// 取出本Adapter的View对象给HidenGroup的Adapter
    			@Override
    			public View getView(int position, View convertView) {
    				return mAdapter.getView(mHidenGroupIndex, position, convertView);
    			}
    
    			@Override
    			public int getCount() {
    				return mAdapter.getCount(mHidenGroupIndex);
    			}
    		};
    		mHidenGroup.setAdapter(mHidenGroupAdapter);
    
    		mShownGroupAdapter = new RandomLayout.Adapter() {
    			// 取出本Adapter的View对象给ShownGroup的Adapter
    			@Override
    			public View getView(int position, View convertView) {
    				return mAdapter.getView(mShownGroupIndex, position, convertView);
    			}
    
    			@Override
    			public int getCount() {
    				return mAdapter.getCount(mShownGroupIndex);
    			}
    		};
    		mShownGroup.setAdapter(mShownGroupAdapter);
    	}

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


    /** 构造方法 */
    	public RandomLayout(Context context) {
    		super(context);
    		init();
    	}

    /** 初始化方法 */
    	private void init() {
    		mLayouted = false;
    		mRdm = new Random();
    		setRegularity(1, 1);
    		mFixedViews = new HashSet<View>();
    		mRecycledViews = new LinkedList<View>();
    	}

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


    /** 设置数据源 */
    	public void setAdapter(Adapter adapter) {
    		this.mAdapter = adapter;
    	}

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


    /** 给指定的Group设置动画 */
    	public void setGroup(int groupIndex, boolean playAnimation) {
    		switchGroup(groupIndex, playAnimation, mZoomInNearAnim, mZoomInAwayAnim);
    	}

    /** 给下一个Group设置进出动画 */
    	private void switchGroup(int newGroupIndex, boolean playAnimation, Animation inAnim,
    			Animation outAnim) {
    		if (newGroupIndex < 0 || newGroupIndex >= mGroupCount) {
    			return;
    		}
    		// 把当前显示Group角标设置为隐藏的
    		mHidenGroupIndex = mShownGroupIndex;
    		// 把下一个Group角标设置为显示的
    		mShownGroupIndex = newGroupIndex;
    		// 交换两个Group
    		RandomLayout temp = mShownGroup;
    		mShownGroup = mHidenGroup;
    		mShownGroup.setAdapter(mShownGroupAdapter);
    		mHidenGroup = temp;
    		mHidenGroup.setAdapter(mHidenGroupAdapter);
    		// 刷新显示的Group
    		mShownGroup.refresh();
    		// 显示Group
    		mShownGroup.setVisibility(View.VISIBLE);
    
    		// 启动动画
    		if (playAnimation) {
    			if (mShownGroup.hasLayouted()) {
    				mShownGroup.startAnimation(inAnim);
    			}
    			mHidenGroup.startAnimation(outAnim);
    		} else {
    			mHidenGroup.setVisibility(View.GONE);
    		}
    	}

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


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


    /** 设置隐藏组和显示组的x和y的规则 */
    	public void setRegularity(int xRegularity, int yRegularity) {
    		mHidenGroup.setRegularity(xRegularity, yRegularity);
    		mShownGroup.setRegularity(xRegularity, yRegularity);
    	}

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


    /** 设置mXRegularity和mXRegularity。确定区域的个数 */
    	public void setRegularity(int xRegularity, int yRegularity) {
    		if (xRegularity > 1) {
    			this.mXRegularity = xRegularity;
    		} else {
    			this.mXRegularity = 1;
    		}
    		if (yRegularity > 1) {
    			this.mYRegularity = yRegularity;
    		} else {
    			this.mYRegularity = 1;
    		}
    		this.mAreaCount = mXRegularity * mYRegularity;// 个数等于x方向的个数*y方向的个数
    		this.mAreaDensity = new int[mYRegularity][mXRegularity];// 存放区域的二维数组
    	}

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


    /** 确定子View的位置,这个就是区域分布的关键 */
    	@Override
    	public void onLayout(boolean changed, int l, int t, int r, int b) {
    		final int count = getChildCount();
    		// 确定自身的宽高
    		int thisW = r - l - this.getPaddingLeft() - this.getPaddingRight();
    		int thisH = b - t - this.getPaddingTop() - this.getPaddingBottom();
    		// 自身内容区域的右边和下边
    		int contentRight = r - getPaddingRight();
    		int contentBottom = b - getPaddingBottom();
    		// 依照顺序存放把区域存放到集合中
    		List<Integer> availAreas = new ArrayList<Integer>(mAreaCount);
    		for (int i = 0; i < mAreaCount; i++) {
    			availAreas.add(i);
    		}
    
    		int areaCapacity = (count + 1) / mAreaCount + 1; // 区域密度,表示一个区域内能够放几个View,+1表示至少要放一个
    		int availAreaCount = mAreaCount; // 可用的区域个数
    
    		for (int i = 0; i < count; i++) {
    			final View child = getChildAt(i);
    			if (child.getVisibility() == View.GONE) { // gone掉的view是不參与布局
    				continue;
    			}
    
    			if (!mFixedViews.contains(child)) {// mFixedViews用于存放已经确定好位置的View,存到了就不是必需再次存放
    				LayoutParams params = (LayoutParams) child.getLayoutParams();
    				// 先測量子View的大小
    				int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(this.getMeasuredWidth(), MeasureSpec.AT_MOST);// 为子View准备測量的參数
    				int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(this.getMeasuredHeight(), MeasureSpec.AT_MOST);
    				child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    				// 子View測量之后的宽和高
    				int childW = child.getMeasuredWidth();
    				int childH = child.getMeasuredHeight();
    				// 用自身的高度去除以分配值,能够算出每个区域的宽和高
    				float colW = thisW / (float) mXRegularity;
    				float rowH = thisH / (float) mYRegularity;
    
    				while (availAreaCount > 0) { // 假设使用区域大于0。就能够为子View尝试分配
    					int arrayIdx = mRdm.nextInt(availAreaCount);// 随机一个list中的位置
    					int areaIdx = availAreas.get(arrayIdx);// 再依据list中的位置获取一个区域编号
    					int col = areaIdx % mXRegularity;// 计算出在二维数组中的位置
    					int row = areaIdx / mXRegularity;
    					if (mAreaDensity[row][col] < areaCapacity) {// 区域密度未超过限定。将view置入该区域
    						int xOffset = (int) colW - childW; // 区域宽度 和 子View的宽度差值,差值能够用来做区域内的位置随机
    						if (xOffset <= 0) {// 说明子View的宽比較大
    							xOffset = 1;
    						}
    						int yOffset = (int) rowH - childH;
    						if (yOffset <= 0) {// 说明子View的高比較大
    							yOffset = 1;
    						}
    						// 确定左边。等于区域宽度*左边的区域
    						params.mLeft = getPaddingLeft() + (int) (colW * col + mRdm.nextInt(xOffset));
    						int rightEdge = contentRight - childW;
    						if (params.mLeft > rightEdge) {// 加上子View的宽度后不能超出右边界
    							params.mLeft = rightEdge;
    						}
    						params.mRight = params.mLeft + childW;
    
    						params.mTop = getPaddingTop() + (int) (rowH * row + mRdm.nextInt(yOffset));
    						int bottomEdge = contentBottom - childH;
    						if (params.mTop > bottomEdge) {// 加上子View的宽度后不能超出右边界
    							params.mTop = bottomEdge;
    						}
    						params.mBottom = params.mTop + childH;
    
    						if (!isOverlap(params)) {// 推断是否和别的View重叠了
    							mAreaDensity[row][col]++;// 没有重叠,把该区域的密度加1
    							child.layout(params.mLeft, params.mTop, params.mRight, params.mBottom);// 布局子View
    							mFixedViews.add(child);// 加入到已经布局的集合中
    							break;
    						} else {// 假设重叠了,把该区域移除。
    							availAreas.remove(arrayIdx);
    							availAreaCount--;
    						}
    					} else {// 区域密度超过限定,将该区域从可选区域中移除
    						availAreas.remove(arrayIdx);
    						availAreaCount--;
    					}
    				}
    			}
    		}
    		mLayouted = true;
    	}

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


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


    @Override
    	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    		int centerX = getMeasuredWidth() / 2;
    		int centerY = getMeasuredWidth() / 2;
    
    		int x1 = (int) e1.getX() - centerX;
    		int y1 = (int) e1.getY() - centerY;
    		int x2 = (int) e2.getX() - centerX;
    		int y2 = (int) e2.getY() - centerY;
    
    		if ((x1 * x1 + y1 * y1) > (x2 * x2 + y2 * y2)) {
    			zoomOut();
    		} else {
    			zoomIn();
    		}
    		return true;
    	}

    /** 给Group设置动画入 */
    	public void zoomIn() {
    		final int nextGroupIndex = mAdapter.getNextGroupOnZoom(mShownGroupIndex, true);
    		switchGroup(nextGroupIndex, true, mZoomInNearAnim, mZoomInAwayAnim);
    	}
    
    	/** 给Group设置出动画 */
    	public void zoomOut() {
    		final int nextGroupIndex = mAdapter.getNextGroupOnZoom(mShownGroupIndex, false);
    		switchGroup(nextGroupIndex, true, mZoomOutNearAnim, mZoomOutAwayAnim);
    	}

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


    本文最后附上Demo以供參考。




  • 相关阅读:
    Net包管理NuGet(3)搭建私服及引用私服的包
    MyMql 下载以及配置
    Oracle 环境部署 以及数据库创建 ,用户新建和权限分配
    VUE.JS 环境配置
    .NET WEB API 简单搭建
    C# Timer 定时任务
    RemoTing 搭建简单实现
    MVC+EF三层+抽象工厂
    ASP.NET MVC SignalR 简单聊天推送笔记
    .net Mvc Dapper 方法封装
  • 原文地址:https://www.cnblogs.com/brucemengbm/p/7403684.html
Copyright © 2020-2023  润新知