ANDROID自定义视图——仿瀑布布局(附源码)

简介:

在自定义view的时候,其实很简单,只需要知道3步骤:

1.测量——onMeasure():决定View的大小

2.布局——onLayout():决定View在ViewGroup中的位置

3.绘制——onDraw():如何绘制这个View。

第3步的onDraw系统已经封装的很好了,基本不用我们来操心,只需要专注到1,2两个步骤就中好了。

第一步的测量,可以参考:(ANDROID自定义视图——onMeasure,MeasureSpec源码 流程 思路详解

第二步的布局,可以参考:(ANDROID自定义视图——onLayout源码 流程 思路详解

下面来介绍是如何通过之前学习的onMeasure和onLayout去自定义一个仿瀑布型的自定义视图。

效果图:

 
        

 
  

第一个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中,道理应该是相同的)。

如图还可以点击观看详细的比例情况

大家还可以点击右上角的Profile Node查看View的执行效率(视图上面的三个小圆点就是)。

hierarchyviewer还可以查看详细的屏幕画面,具体到像素级别的问题都可以通过它发现。

如果看一个比较复杂的代码时也可以使用hierarchyviewer快速了解视图结构。

DEBUG+找张纸拿笔算:

使用hierarchyviewer的主要作用就是为了调错用的,而具体的宽高计算还需要不停的跟踪debug,而算法和思路就需要用纸笔慢慢设计计算了(除非你有一个牛逼的大脑)

总结:

之前写完onMeasure和onLayout的内容时就想写一个小例子,本来计划写个FlowLayout(流布局)的例子。但是前几个星期发现有人刚写了一个,所以也是借着流布局的思路写出来这个,写完发现这不就是Waterfall Layout(瀑布布局)么。

这个例子有很多可以改进的地方,比如还不能动态添加和删除视图,列值也不能动态设置。没有根据屏幕大小按比例放大/缩小每个Card视图。而且Card视图也应该在Fragment中,然后再添加到自定义View中去。以后有时间我会好好改进一下。

有人要下载的话就看看逻辑就好了,那几个CardLayout我就是东挪西凑弄出来的,里面的代码简直不忍直视大家就忽略好了。

另外这个工程是eclipse建立,然后导入到Android Studio中编写的。正常导入是没问题的,如果有问题的话试试把build.gradle等文件删除再导入,实在不好使就新建个工程把几个关键类复制进去吧。。。

代码点击下载

ANDROID自定义视图——仿瀑布布局(附源码),布布扣,bubuko.com

时间: 2024-10-15 05:20:36

ANDROID自定义视图——仿瀑布布局(附源码)的相关文章

Android跟踪球-手势移动图片-自定义控件(附源码)

由于我不会制作动画图片,所以先放几及其不具备代表性的展示图片. 我以前的思路是通过动态的设置xy坐标通过手势移动来识别,但是我后来试了一下,发现运行效果极差.所以偷闲做了下这个跟踪球控件,其实实现十分简单.只要大家熟悉自定义控件的使用以及手势识别.基本上就ok了. 现在我们看下这个控件的源码TouchMoveView.java package com.fay.touchmove; import android.annotation.SuppressLint; import android.con

android版高仿淘宝客户端源码V2.3

android版高仿淘宝客户端源码V2.3,这个版本我已经更新到2.3了,源码也上传到源码天堂那里了,大家可以看一下吧,该应用实现了我们常用的购物功能了,也就是在手机上进行网购的流程的,如查看产品(浏览),下订单,进行付款等流程,该应用一一实现了,同时还可以远程读取图片功能,和实时监控网络状态等操作,大家如果有什么不同的意见可以留下,我们会定时来查看. 原文地址:http://www.cnblogs.com/androidioscom/p/3613035.html [1].[代码] [Java]

Android使用xml自定义软键盘效果(附源码)

Android使用xml自定义软键盘效果原理: 1,软键盘其实是个控件,使用android.inputmethodserver.KeyboardView类定义. 2,主布局中使用帧布局,当我们需要显示软键盘时设置为可见,不需要时设置为不可见. 3,编写xml文件,定义键盘内容.使用xml文件填充KeyBoardView布局 4,设置EditText的监听事件. 完成键盘开发. 上效果图: 1,源码研究android.inputmethodserver.KeyboardView: /* * Cop

C#使用Xamarin开发可移植移动应用进阶篇(7.使用布局渲染器,修改默认布局),附源码

前言 系列目录 C#使用Xamarin开发可移植移动应用目录 源码地址:https://github.com/l2999019/DemoApp 可以Star一下,随意 - - 说点什么.. 本篇..基本可以算是Xamarin在应用开发过程中的核心了..真的很很很重要.. 想学习的..想用的..建议仔细阅读..嗯..打酱油的 ..快速滑倒下面点个推荐 - - 哈哈哈... 今天的学习内容? 也只讲一个,关于Xamarin.Forms针对各个平台如何进行可定制化的布局操作. 也就是针对某个平台的细颗

(转)Android与js交互实例(附源码)

本文转载于:http://blog.csdn.net/ithomer/article/details/8737999# Android 中可以通过webview来实现和js的交互,在程序中调用js代码,只需要将webview控件的支持js的属性设置为true Android(Java)与JavaScript(HTML)交互有四种情况: 1) Android(Java)调用HTML中js代码 2) Android(Java)调用HTML中js代码(带参数) 3) HTML中js调用Android(

struts2内置拦截器和自定义拦截器详解(附源码)

一.Struts2内置拦截器 Struts2中内置类许多的拦截器,它们提供了许多Struts2的核心功能和可选的高级特 性.这些内置的拦截器在struts-default.xml中配置.只有配置了拦截器,拦截器才可以正常的工作和运行.Struts 2已经为您提供丰富多样的,功能齐全的拦截器实现.大家可以至struts2的jar包内的struts-default.xml查看关于默认的拦截器与 拦截器链的配置.内置拦截器虽然在struts2中都定义了,但是并不是都起作用的.因为并不是所有拦截器都被加

【转】ANDROID自定义视图——onMeasure,MeasureSpec源码 流程 思路详解

原文地址:http://blog.csdn.net/a396901990/article/details/36475213 简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量——onMeasure():决定View的大小 2.布局——onLayout():决定View在ViewGroup中的位置 3.绘制——onDraw():如何绘制这个View. 而第3步的onDraw系统已经封装的很好了,基本不用我们来操心,只需要专注到1,2两个步骤就中好了. 而这篇文章就来谈谈第一步

【转】ANDROID自定义视图——onLayout源码 流程 思路详解

转载(http://blog.csdn.net/a396901990) 简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量——onMeasure():决定View的大小 2.布局——onLayout():决定View在ViewGroup中的位置 3.绘制——onDraw():如何绘制这个View. 而第3步的onDraw系统已经封装的很好了,基本不用我们来操心,只需要专注到1,2两个步骤就中好了. 第一步的测量,可以参考我之前的文章:(ANDROID自定义视图——onMea

Android应用经典主界面框架之一:仿QQ (使用Fragment, 附源码)

最近反复研究日常经典必用的几个android app,从主界面带来的交互方式入手进行分析,我将其大致分为三类.今天记录第一种方式,即主界面下面有几个tab页,最上端是标题栏,tab页和tab页之间不是通过滑动切换的,而是通过点击切换tab页.早期这种架构一直是使用tabhost+activitygroup来使用,随着fragment的出现及google官方也大力推荐使用fragment,后者大有代替前者之势.本文也使用fragment进行搭建,标题中的"经典"指这种交互经典,非本文的代