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

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

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. 深入理解Python中赋值、深拷贝(deepcopy)、浅拷贝(copy)

    赋值 python跟java中的变量本质是不一样的,Python的变量实质上是一个指针(int型或str型),而java的变量是一个可操作的存储空间. a = 123b = a print(id(a) ...

  2. 2、Qt Project之鼠标事件监控

    鼠标事件监控: 对于鼠标时间监控的相关操作,我们在简历工程的时候需要选择的是QWidget基类,不选择QMainWindow基类,如下所示: Base class:QWidget Step1:我们首先 ...

  3. SVM—PK—BP:SVR(better)和BP两种方法比较且实现建筑物钢筋混凝土抗压强度预测—Jason niu

    load concrete_data.mat n = randperm(size(attributes,2)); p_train = attributes(:,n(1:80))'; t_train = ...

  4. Python并发复习2 - 多线程模块threading

    一.多线程的调用 threading 模块建立在thread 模块之上.thread模块以低级.原始的方式来处理和控制线程,而threading 模块通过对thread进行二次封装, 提供了更方便的a ...

  5. P1967 货车运输

    P1967 货车运输最大生成树+lca+并查集 #include<iostream> #include<cstdio> #include<queue> #inclu ...

  6. Linux系统开发之路 - 下

    5.Ubuntu安装好之后,就可以进行开发环境的搭建.(坚持看完有彩蛋,(>--..--<).jpg). 1)首先安装Nodejs和Npm. 打开浏览器输入nodejs.org,进入页面会 ...

  7. ps无法存储为PNG

    ps无法存储为PNG,通过各种手段看是否能解决: 第一种:合并图层以后再存储: 第二种:更改存储的名称: 第三种:直接重启ps.

  8. 765. 有效的三角形.md

    描述 给出三个整数 a, b, c, 如果它们可以构成三角形,返回 true. 三角形的定义 (Wikipedia) 样例 给定 a = 2, b = 3, c = 4 返回 true 给定 a = ...

  9. Centos6.5部署Rsyslog+LogAnalyzer收集网络及系统日志

    1. 介绍 Rsyslog是比syslog功能更强大的日志记录系统,可以将日志输出到文件,数据库和其它程序.可以使用rsyslog替换系统自带的syslog. LogAnalyzer 是一个 sysl ...

  10. 电脑用bat脚本给手机批量自动安装apk文件 autoInstall.bat

    ------创建这个文件autoInstall.bat 内容如下: @ECHO off @REM 将adb.exe添加到PATH中ECHO 初始化…@SET PATH=%PATH%;%CD%\Adb@ ...