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

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

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

然后看使用方法代码:

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以供參考。

android自己定义控件之飞入飞出控件的更多相关文章

  1. CSS3实现Tooltip提示框飞入飞出动画

    原文:CSS3实现Tooltip提示框飞入飞出动画 我们见过很多利用背景图片制作的Tooltip提示框,但是缺点是扩展比较麻烦,要经常改动图片.还有就是利用多层CSS的叠加实现,但是效果比较生硬,外观 ...

  2. HTMO DOM部分---小练习;列表之间移动、日期选择、好友选中、滑动效果、滚动条效果、飞入飞出效果。

    一:列表之间数据移动 第一个列表里面有内容,第二个里面没有 实现功能: 点击左侧列表选中一项内容,点击按钮,复制到右侧 点击复制所有按钮,将左侧列表所有数据,复制到右侧 扩展功能:右侧列表实现去重复 ...

  3. Android 自己定义圆圈进度并显示百分比例控件(纯代码实现)

    首先,感谢公司能给我闲暇的时间,来稳固我的技术,让我不断的去探索研究,在此不胜感激. 先不说实现功能,上图看看效果 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZ ...

  4. 【转】C#窗体飞入飞出的动画效果(Api)

    [System.Runtime.InteropServices.DllImport("user32")] private static extern bool AnimateWin ...

  5. css过渡——实现元素的飞入飞出

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. Android 自己定义ScrollView ListView 体验各种纵向滑动的需求

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38950509.本文出自[张鸿洋的博客] 1.概述 群里的一个哥们有个需求是这种: ...

  7. Android 自己定义RecyclerView 实现真正的Gallery效果

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38173061 .本文出自:[张鸿洋的博客] 上一篇博客我使用自己定义Horizo ...

  8. Android自己定义控件系列五:自己定义绚丽水波纹效果

    尊重原创!转载请注明出处:http://blog.csdn.net/cyp331203/article/details/41114551 今天我们来利用Android自己定义控件实现一个比較有趣的效果 ...

  9. Android自己定义控件:进度条的四种实现方式

    前三种实现方式代码出自: http://stormzhang.com/openandroid/2013/11/15/android-custom-loading/ (源代码下载)http://down ...

随机推荐

  1. ActiveMQ在windows下启动失败解决方案

    activemq.xml文件中的 <transportConnectors> <!-- DOS protection, limit concurrent connections to ...

  2. 爬虫2 urllib3 爬取30张百度图片

    import urllib3 import re # 下载百度首页页面的所有图片 # 1. 找到目标数据 # page_url = 'http://image.baidu.com/search/ind ...

  3. Peter's smokes -poj 2509

    题意:彼得有n支雪茄,每k个烟头可以换一支新雪茄,问彼得最多可以吸多少支雪茄 ? 当时自己做时,错在了直接在while循环开始前,便将雪茄的初始数量给加上了,然而应该是先处理后再加上最终剩余的雪茄数量 ...

  4. 1402 后缀数组 (hash+二分)

    描述 后缀数组 (SA) 是一种重要的数据结构,通常使用倍增或者DC3算法实现,这超出了我们的讨论范围.在本题中,我们希望使用快排.Hash与二分实现一个简单的 O(n log^2⁡n ) 的后缀数组 ...

  5. Windows10系统网络连接问题

    Thinkpad笔记本Windows10系统突然遇到网络连接问题: 问题:今天同事遇到一个WiFi连接问题,ThinkPad笔记本在公司和家里面都无法连接WiFi 寻找问题: 1.检查硬件问题 首先我 ...

  6. 改变字体大小实现自适应之js方案A

    一.元素大小有两种写法 1.写结果:设计师给的移动端页面sketch设计稿一般是750px宽度,在sublime编辑器里,设置cssrem或rem-unit插件为56px的字体大小.做页面时,设计稿是 ...

  7. POJ1742----Coins

    背包专题:http://www.cnblogs.com/qq188380780/p/6409474.html //多重背包 #include<cstdio> ],a[][]; int Ro ...

  8. emitted value instead of an instance of error the scope attribute for scoped slots webpack babel polyfill

    api20180803.vue emitted value instead of an instance of error the scope attribute for scoped slots h ...

  9. BZOJ.5338.[TJOI2018]xor(可持久化Trie)

    BZOJ LOJ 洛谷 惊了,18年了还有省选出模板题吗= = 做这题就是练模板的,我就知道我忘的差不多了 询问一就用以DFS序为前缀得到的可持久化Trie做,询问二很经典的树上差分. 注意求询问二的 ...

  10. Python3基础-Python作用域详述(转载)

    转载文章 转载文章 作者:骏马金龙 出处:http://www.cnblogs.com/f-ck-need-u/p/9925021.html Python作用域详述 作用域是指变量的生效范围,例如本地 ...