• 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去自己定义一个仿瀑布型的自己定义视图。


    效果图:


              


        

    第一个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 pathsdk ools

    以下放几张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我就是东挪西凑弄出来的。里面的代码简直不忍直视大家就忽略好了。

    另外这个project是eclipse建立,然后导入到Android Studio中编写的。

    正常导入是没问题的,假设有问题的话试试把build.gradle等文件删除再导入,实在不好使就新建个project把几个关键类复制进去吧。。。


    代码点击下载



    版权声明:本文博客原创文章,博客,未经同意,不得转载。

  • 相关阅读:
    打开Intellij Idea 2020.1 提示 cannot load a jdk class: com.sun.jdi.Field
    win10触摸板设置为连接鼠标不打开后就自动关闭
    git配置账号
    HTTP请求中的Form Data与Request Payload的区别
    VUE—axios自定义请求配置—3、transformRequest在向服务器发送前,修改请求数据(图文详情)
    在Sass中,我们可以使用“@for”来实现循环操作
    vue项目引入背景图报Module not found: Error: Can't resolve './src/assets/theme/logo_blue.png' in'xxx'错误
    Importing code style from ESLint
    ESLint fix自动修复所有格式问题
    【T07】不要低估tcp的性能
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/4664300.html
Copyright © 2020-2023  润新知