ANDROID定义自己的观点——模仿瀑布布局(源代码)
转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持!
简单介绍:
在自己定义view的时候,事实上非常easy,仅仅须要知道3步骤:
1.測量——onMeasure():决定View的大小
2.布局——onLayout():决定View在ViewGroup中的位置
3.绘制——onDraw():怎样绘制这个View。
第3步的onDraw系统已经封装的非常好了,基本不用我们来担心,仅仅须要专注到1,2两个步骤就中好了。
第一步的測量,能够參考:(ANDROID自己定义视图——onMeasure,MeasureSpec源代码 流程 思路具体解释)
第二步的布局,能够參考:(ANDROID自己定义视图——onLayout源代码 流程 思路具体解释)
以下来介绍是怎样通过之前学习的onMeasure和onLayout去自己定义一个仿瀑布型的自己定义视图。
效果图:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYTM5NjkwMTk5MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="400" height="300" alt="">
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYTM5NjkwMTk5MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="400" height="300" alt="">
第一个gif图是在手机模拟器上,因为手机屏幕小所以在竖直状态下每行显示一个,在横屏时每行显示两个。
而在平板上时候因为屏幕非常大,所以能够依据详细尺寸和须要调整每行显示的view数。
本例仅仅是简单的显示,可是这里能够把每一个view当做是一个Card。
每一个Card用一个fragment控制。这样就能够在一个大屏中按需求显示很多其它的Fragment,这样就不用在ViewPager中左右滑动来显示fragment。
代码分析:
主Activity:
@Override
protected void onCreate( Bundle savedInstanceState )
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
}
main_layout.xml:
<? xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:auto="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"> <com.gxy.autolayout.MyScrollView
auto:columns="1"
android:id="@+id/myScrollView"
android:layout_margin="5dip"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.gxy.autolayout.MyScrollView> </LinearLayout>
没什么特别的,仅仅是在一个LinearLayout中增加了一个自己定义的View——MyScrollView。并且在该View中有个自己定义属性columns,它表示每行显示多少个View。
关于自己定义属性网上非常多,我这里就不浪费时间了。
MyScrollView:
/**
* 该类继承自ScrollView,目的是为了能够滑动我们自己定义的视图
*/
public class MyScrollView
extends ScrollView
{
int columns = 0; public MyScrollView( Context context )
{
super(context);
} public MyScrollView( Context context, AttributeSet attrs )
{
super(context, attrs);
// 取出布局中自己定义的属性
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyScrollView);
columns = typedArray.getInteger(R.styleable.MyScrollView_columns, 0);
typedArray.recycle();
// 初始化视图
initView(columns);
} private void initView( int columns )
{
// 建立一个LinearLayout作为ScrollView的顶层视图(由于ScrollView仅仅能够有一个子ViewGroup)
LinearLayout linearLayout = new LinearLayout(getContext());
linearLayout.setOrientation(LinearLayout.VERTICAL);
// 在LinearLayout中增加一个自己定义视图AutoCardLayout(我们的逻辑所有在这个自己定义视图类中)
linearLayout.addView(new AutoCardLayout(getContext(), columns));
addView(linearLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
}
AutoCardLayout:
/**
* AutoCardLayout继承自ViewGroup,主要作用就是依据column的值动态的排列每一个card视图
*/
public class AutoCardLayout
extends ViewGroup { // 每行显示的列数
int column = 0;
// 每一个Card的横向间距
int margin = 20; // 构造方法中增加5个已经定义好的布局(这里就是为了图方便。就直接扔构造方法里了)
public AutoCardLayout(Context context, int columns) {
super(context);
this.column = columns;
View v1 = LayoutInflater.from(context).inflate(R.layout.card_layout1, null);
View v2 = LayoutInflater.from(context).inflate(R.layout.card_layout2, null);
View v3 = LayoutInflater.from(context).inflate(R.layout.card_layout3, null);
View v4 = LayoutInflater.from(context).inflate(R.layout.card_layout4, null);
View v5 = LayoutInflater.from(context).inflate(R.layout.card_layout5, null); addView(v1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
addView(v2, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
addView(v3, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
addView(v4, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
addView(v5, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
} // 重写的onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { } // 重写的onLayout方法
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) { }
}
onMeasure和onLayout是本文的重点,所以单独拿出来解说。
只是为了更好的理解,我先把思路解说一下:
1. 此布局类似与瀑布布局,从左到右排序。但会重上到下按列对齐(按列对齐这点很重要)
2. 在onMeasure方法中须要測量每一个子View的宽。每一个子View的宽应该是同样的,和每行显示的列数和间距有关
3. 在onMeasure方法中我们无法測量出每一个子View的測量高度(MeasureSpec.getSize(heightMeasureSpec)=0)。由于在ScrollView中高度不确定(个人理解,希望指正)
4. 在onMeasure方法须要測量父视图的大小,宽度是确定的。主要測量实际的高度(父View高度是最长列子View的高度之和)
5. 在onLayout方法中须要依据每一个View所在的位置进行布局
onMeasure
// 重写的onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 得到父View的实际測量宽
int width = MeasureSpec.getSize(widthMeasureSpec); // (width - (column - 1) * margin) / column 得到每一个子View的宽度(思路:父View减去全部的间距再除以列数)
// 依据子View宽度和測量模式确定出子View的具体測量宽
int colWidthSpec = MeasureSpec.makeMeasureSpec((width - (column - 1) * margin) / column, MeasureSpec.EXACTLY);
// 由于測量不出来子View的高度,所以这里设置其測量模式为未指定得到具体測量高
int colHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); // 列数组,代表这一列全部View的高度
int[] colPosition = new int[column]; // 循环全部子View
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
// 调用子View的measure方法并传入之前计算好的值进行測量
child.measure(colWidthSpec, colHeightSpec); // (i + 1 + column) % column 这段代码就是通过当前的View和列数算出当前View是在第几列(多加了一个column值是防止被余数小于余数)
// 将对应列的子View高度相加
colPosition[(i + 1 + column) % column] += child.getMeasuredHeight();
} // 父View的长度值
int height = 0;
// 以下代码计算出列数组中最长列的值,最长列的值就是父View的高度
for (int j = 0; j < column; j++) {
height = colPosition[j] > height ? colPosition[j] : height;
} // 依据计算得到的宽高值測量父View
setMeasuredDimension(width, height);
}
onLayout
// 重写的onLayout方法
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 列数组,代表这一列全部View的高度
int[] colPosition = new int[column]; // 循环全部子View
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i); // 得到子View的宽高,
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight(); // 得到当前View在列数组中的下标(假设余数为0则是最后一列)
int index = (i + 1 + column) % column == 0 ? column - 1 : (i + 1 + column) % column - 1;
// 将子View的高度加到列高度中
colPosition[index] += height; // 计算当前View的左上右下值,传入layout方法中进行布局
// 详细思路我在之前介绍onlayout的文章提过,仅仅要知道left值和top值还有子View的宽高值就能够确定出right和bottom值(right = left + width,bottom = top + height)
int left = l + index * (width + margin);
int right = column == index + 1 ? r : left + width;
int top = t + colPosition[index] - height;
int bottom = top + height;
child.layout(left, top, right, bottom);
}
}
onMeasure和onLayout就介绍完了,我自觉得这个算法还是非常不错的。
以下介绍一下我在自己定义View时的技巧:
1. hierarchyviewer
2. DEBUG + 找张纸拿笔算
hierarchyviewer
这个不用多说,自己定义View时的神器
大家能够在例如以下文件夹中找到它:your sdk path\sdk\tools
以下放几张hierarchyviewer的截图,顺便看看这个样例的视图结构:
最外层结构
如图,每一个子视图都是在FrameLayout视图之上。否则不能正确測量(什么原因请大神指点)。
我为了方便直接把FrameLayout写在每一个Cardlayout中,事实上比較好的做法是应该在代码中new一个FrameLayout然后再addView(看代码时fragment常常包裹在一个FrameLayout中。道理应该是同样的)。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYTM5NjkwMTk5MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
如图还能够点击观看具体的比例情况
大家还能够点击右上角的Profile Node查看View的运行效率(视图上面的三个小圆点就是)。
hierarchyviewer还能够查看具体的屏幕画面,具体到像素级别的问题都能够通过它发现。
假设看一个比較复杂的代码时也能够使用hierarchyviewer高速了解视图结构。
DEBUG+找张纸拿笔算:
使用hierarchyviewer的主要作用就是为了调错用的。而详细的宽高计算还须要不停的跟踪debug,而算法和思路就须要用纸笔慢慢设计计算了(除非你有一个牛逼的大脑)
总结:
之前写完onMeasure和onLayout的内容时就想写一个小样例,本来计划写个FlowLayout(流布局)的样例。
可是前几个星期发现有人刚写了一个。所以也是借着流布局的思路写出来这个。写完发现这不就是Waterfall Layout(瀑布布局)么。
这个样例有非常多能够改进的地方,比方还不能动态加入和删除视图,列值也不能动态设置。没有依据屏幕大小按比例放大/缩小每一个Card视图。并且Card视图也应该在Fragment中,然后再加入到自己定义View中去。以后有时间我会好好改进一下。
有人要下载的话就看看逻辑就好了。那几个CardLayout我就是东挪西凑弄出来的。里面的代码简直不忍直视大家就忽略好了。
另外这个project是eclipse建立,然后导入到Android Studio中编写的。
正常导入是没问题的,假设有问题的话试试把build.gradle等文件删除再导入,实在不好使就新建个project把几个关键类复制进去吧。。。
版权声明:本文博客原创文章,博客,未经同意,不得转载。
ANDROID定义自己的观点——模仿瀑布布局(源代码)的更多相关文章
- ANDROID定义自己的看法——onMeasure,MeasureSpec源代码 过程 思考具体解释
一个简短的引论: 在他们的定义view什么时候,其实很easy,只需要知道3: 1.測量--onMeasure():决定View的大小 2.布局--onLayout():决定View在ViewGrou ...
- 【Android】14.0 UI开发(五)——列表控件RecyclerView的瀑布布局排列实现
1.0 列表控件RecyclerView的瀑布布局排列实现,关键词StaggeredGridLayoutManager LinearLayoutManager 实现顺序布局 GridLayoutMan ...
- 【Android UI】Android开发之View的几种布局方式及实践
引言 通过前面两篇: Android 开发之旅:又见Hello World! Android 开发之旅:深入分析布局文件&又是“Hello World!” 我们对Android应用程序运行原理 ...
- Android四大组件之Activity(活动)及其布局的创建与加载布局
Android四大组件之Activity(活动)及其布局的创建与加载布局 什么是Activity ? 活动(Activity)是包含用户界面的组件,主要用于和用户进行交互的,一个应用程序中可以包含零个 ...
- Android 开发:view的几种布局方式及实践
View的几种布局显示方法,以后就不会在针对布局方面做过多的介绍.View的布局显示方式有下面几种:线性布局(Linear Layout).相对布局(Relative Layout).表格布局(Tab ...
- Android开发:View的几种布局及实践
引言 View的布局显示方式有下面几种:线性布局(Linear Layout).相对布局(Relative Layout).表格布局(Table Layout).网格视图(Grid View).标签布 ...
- 【Android 应用开发】AndroidUI设计之 布局管理器 - 详细解析布局实现
写完博客的总结 : 以前没有弄清楚的概念清晰化 父容器与本容器属性 : android_layout...属性是本容器的属性, 定义在这个布局管理器的LayoutParams内部类中, 每个布局管理器 ...
- Android学习笔记(三) UI布局
每一个布局都有其适合的方式,另外,这几个布局元素可以相互嵌套应用,做出美观的界面. 一.线性布局(LinearLayout) 线性布局,这个东西,从外框上可以理解为一个div,他首先是一个一个从上往下 ...
- Android动画效果之自定义ViewGroup添加布局动画
前言: 前面几篇文章介绍了补间动画.逐帧动画.属性动画,大部分都是针对View来实现的动画,那么该如何为了一个ViewGroup添加动画呢?今天结合自定义ViewGroup来学习一下布局动画.本文将通 ...
随机推荐
- mfc 链接时错误 文件函数重复定义
我在HeaderFile里新建了一个函数,然后在程序里调用,一直出现这个错误,说这个函数重复定义, 发现是VS自动加到External dependencies里面了.把HeaderFile里的函数文 ...
- 动态规划,而已! CodeForces 433B - Kuriyama Mirai's Stones
Kuriyama Mirai has killed many monsters and got many (namely n) stones. She numbers the stones from ...
- ANDROID嵌入式应用Unity3D视图(画廊3D模型)
转载请注明来自大型玉米的博客文章(http://blog.csdn.net/a396901990),谢谢支持! 效果展示: watermark/2/text/aHR0cDovL2Jsb2cuY3N ...
- 通过java.util.concurrent写多线程程序
在JDK 1.5之前,要实现多线程的功能,得用到Thread这个类,通过这个类设计多线程程序,需要考虑性能,死锁,资源等很多因素,一句话,就是相当麻烦,而且很容易出问题.所幸的是,在JDK1.5之后, ...
- enq: TX - row lock contention 参数P1,P2,P3说明
enq: TX - row lock contention三个参数,例如,下面的等待事件 * P1 = name|mode <<<<<<< ...
- QtQuick桌面应用程序开发指导 3)达到UI而功能_B 4)动态管理Note物_A
3.2 把Page Item和Marker Item绑定 之前我们实现了PagePanel组件, 使用了三个state来切换Page组件的opacity属性; 这一步我们会使用Marker和Marke ...
- openstack 网络简史
openstack 网络简史 研究openstack有2个月的时间,这段时间从网上获取N多宝贵资料,对我的学习有非常大帮助,在加上我自己的研究,最终对openstack整个网络体系有了个浅显的认识,写 ...
- 网络资源(7) - JAX-WS视频
2014_08_25 http://v.youku.com/v_show/id_XNjMzNDcyMTk2.html 基于JAX-WS编程模型的WebService 1. @WebService注释类 ...
- Git联系oschina托管代码版本号
工作一般使用SVN,近期好像GitHub有些火.看到开源中国上也有Git的开源版本号管理. 另外看到一篇文章说Git 比 SVN 要好. 就想多了解一下Git.顺便也能够把自己平时的一些代码保存在云端 ...
- 【SICP练习】150 练习4.6
练习4-6 原版的 Exercise 4.6. Let expressions are derived expressions, because (let (( ) - ( )) ) is equiv ...