近期呢,本人辭職了。在找工做期間。不幸碰到了這個求職淡季,另外仍是大學生畢業求職的高峯期,簡歷發了無數份卻都石沉大海。寶寶內心那是一個苦啊!html
翻着過去的代碼,本人偶然找到了一個有意思的控件。那時本人尚未寫博客的習慣,現在補上,先看效果圖:java
而後看使用方法代碼:數組
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; } }
接下來纔是正餐,咱們看看StellarMap的實現。StellarMap繼承於FrameLayout:dom
/** 構造方法 */ 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(); }
/** 初始化方法 */ 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); }
依照代碼運行順序來,下一步是設置Adapter:ide
/** 設置本Adapter */ public void setAdapter(Adapter adapter) { mAdapter = adapter; mGroupCount = mAdapter.getGroupCount(); if (mGroupCount > 0) { mShownGroupIndex = 0; } 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); }
/** 構造方法 */ 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>(); }
/** 設置數據源 */ public void setAdapter(Adapter adapter) { this.mAdapter = adapter; }
/** 給指定的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); } }
最後一行代碼。stellarMap.setRegularity(15, 20)方法:佈局
/** 設置隱藏組和顯示組的x和y的規則 */ public void setRegularity(int xRegularity, int yRegularity) { mHidenGroup.setRegularity(xRegularity, yRegularity); mShownGroup.setRegularity(xRegularity, yRegularity); }
/** 設置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];// 存放區域的二維數組 }
/** 肯定子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中增長了手勢。用於用戶滑動的時候給與交互:post
@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); }
本文最後附上Demo以供參考。動畫