Android中View的介绍

均属于笔记,仅供个人参考,有问题欢迎指正,整理模式

一,Android LayoutInflater原理分析,带你一步步深入了解View(一)

先来看一下LayoutInflater的基本用法吧,它的用法非常简单,首先需要获取到LayoutInflater的实例

LayoutInflater layoutInflater = LayoutInflater.from(context); 

得到了LayoutInflater的实例之后就可以调用它的inflate()方法来加载布局了,如下所示:

layoutInflater.inflate(resourceId, root); 

平时我们经常使用layout_width和layout_height来设置View的大小,并且一直都能正常工作,就好像这两个属性确实是用于设置View的大小的。而实际上则不然,它们其实是用于设置View在布局中的大小的,也就是说,首先View必须存在于一个布局中,之后如果将layout_width设置成match_parent表示让View的宽度填充满布局,如果设置成wrap_content表示让View的宽度刚好可以包含其内容,如果设置成具体的数值则View的宽度会变成相应的数值。这也是为什么这两个属性叫作layout_width和layout_height,而不是width和height。

在setContentView()方法中,Android会自动在布局文件的最外层再嵌套一个FrameLayout,所以layout_width和layout_height属性才会有效果。那么我们来证实一下吧,修改MainActivity中的代码,如下所示:

public class MainActivity extends Activity { 


    private LinearLayout mainLayout; 


    @Override 

    protected void onCreate(Bundle savedInstanceState) { 

        super.onCreate(savedInstanceState); 

        setContentView(R.layout.activity_main); 

        mainLayout = (LinearLayout) findViewById(R.id.main_layout); 

        ViewParent viewParent = mainLayout.getParent(); 

        Log.d("TAG", "the parent of mainLayout is " + viewParent); 

    } 


}

打印结果显示其父布局是一个FrameLayout。

虽然setContentView()方法大家都会用,但实际上Android界面显示的原理要比我们所看到的东西复杂得多。任何一个Activity中显示的界面其实主要都由两部分组成,标题栏和内容布局。标题栏就是在很多界面顶部显示的那部分内容,比如刚刚我们的那个例子当中就有标题栏,可以在代码中控制让它是否显示。而内容布局就是一个FrameLayout,这个布局的id叫作content,我们调用setContentView()方法时所传入的布局其实就是放到这个FrameLayout中的,这也是为什么这个方法名叫作setContentView(),而不是叫setView()。

参考:http://blog.csdn.net/guolin_blog/article/details/12921889

二,Android视图绘制流程完全解析,带你一步步深入了解View(二)

1,onMeasure()

在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。

视图大小的控制是由父视图、布局文件、以及视图本身共同完成的,父视图会提供给子视图参考的大小,而开发人员可以在XML文件中指定视图的大小,然后视图本身会对最终的大小进行拍板

2,onLayout()

public class SimpleLayout extends ViewGroup { 

    public SimpleLayout(Context context, AttributeSet attrs) { 

        super(context, attrs); 

    } 


    @Override 

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 

        super.onMeasure(widthMeasureSpec, heightMeasureSpec); 

        if (getChildCount() > 0) { 

            View childView = getChildAt(0); 

            measureChild(childView, widthMeasureSpec, heightMeasureSpec); 

        } 

    } 


    @Override 

    protected void onLayout(boolean changed, int l, int t, int r, int b) { 

        if (getChildCount() > 0) { 

            View childView = getChildAt(0); 

            childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight()); 

        } 

    } 


代码非常的简单,我们来看下具体的逻辑吧。你已经知道,onMeasure()方法会在onLayout()方法之前调用。

调用这个子视图的layout()方法来确定它在SimpleLayout布局中的位置,这里传入的四个参数依次是0、0、childView.getMeasuredWidth()和childView.getMeasuredHeight(),分别代表着子视图在SimpleLayout中左上右下四个点的坐标。其中,调用childView.getMeasuredWidth()和childView.getMeasuredHeight()方法得到的值就是在onMeasure()方法中测量出的宽和高。

首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。

3,onDraw()

public class MyView extends View { 


    private Paint mPaint; 


    public MyView(Context context, AttributeSet attrs) { 

        super(context, attrs); 

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 

    } 


    @Override 

    protected void onDraw(Canvas canvas) { 

        mPaint.setColor(Color.YELLOW); 

        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint); 

        mPaint.setColor(Color.BLUE); 

        mPaint.setTextSize(20); 

        String text = "Hello View"; 

        canvas.drawText(text, 0, getHeight() / 2, mPaint); 

    } 

参考:http://blog.csdn.net/guolin_blog/article/details/16330267

三,Android视图状态及重绘流程分析,带你一步步深入了解View(三)

视图状态的种类非常多,一共有十几种类型,不过多数情况下我们只会使用到其中的几种,因此这里我们也就只去分析最常用的几种视图状态。

1. enabled

表示当前视图是否可用。可以调用setEnable()方法来改变视图的可用状态,传入true表示可用,传入false表示不可用。它们之间最大的区别在于,不可用的视图是无法响应onTouch事件的。

2. focused

表示当前视图是否获得到焦点。通常情况下有两种方法可以让视图获得焦点,即通过键盘的上下左右键切换视图,以及调用requestFocus()方法。而现在的Android手机几乎都没有键盘了,因此基本上只可以使用requestFocus()这个办法来让视图获得焦点了。而requestFocus()方法也不能保证一定可以让视图获得焦点,它会有一个布尔值的返回值,如果返回true说明获得焦点成功,返回false说明获得焦点失败。一般只有视图在focusable和focusable in touch mode同时成立的情况下才能成功获取焦点,比如说EditText。

3. window_focused

表示当前视图是否处于正在交互的窗口中,这个值由系统自动决定,应用程序不能进行改变。

4. selected

表示当前视图是否处于选中状态。一个界面当中可以有多个视图处于选中状态,调用setSelected()方法能够改变视图的选中状态,传入true表示选中,传入false表示未选中。

5. pressed

表示当前视图是否处于按下状态。可以调用setPressed()方法来对这一状态进行改变,传入true表示按下,传入false表示未按下。通常情况下这个状态都是由系统自动赋值的,但开发者也可以自己调用这个方法来进行改变。

invalidate()方法虽然最终会调用到performTraversals()方法中,但这时measure和layout流程是不会重新执行的,因为视图没有强制重新测量的标志位,而且大小也没有发生过变化,所以这时只有draw流程可以得到执行。而如果你希望视图的绘制流程可以完完整整地重新走一遍,就不能使用invalidate()方法,而应该调用requestLayout()了。

参考:http://blog.csdn.net/guolin_blog/article/details/17045157

四, Android自定义View的实现方法,带你一步步深入了解View(四)

如果说要按类型来划分的话,自定义View的实现方式大概可以分为三种,自绘控件、组合控件、以及继承控件。

1,自绘控件

自绘控件的意思就是,这个View上所展现的内容全部都是我们自己绘制出来的。绘制的代码是写在onDraw()方法中的

调用invalidate()方法会导致视图进行重绘,因此onDraw()方法在稍后就将会得到调用。

自定义一个计数器View,这个View可以响应用户的点击事件,并自动记录一共点击了多少次。新建一个CounterView继承自View:

public class CounterView extends View implements OnClickListener { 


    private Paint mPaint; 


    private Rect mBounds; 


    private int mCount; 


    public CounterView(Context context, AttributeSet attrs) { 

        super(context, attrs); 

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 

        mBounds = new Rect(); 

        setOnClickListener(this); 

    } 


    @Override 

    protected void onDraw(Canvas canvas) { 

        super.onDraw(canvas); 

        mPaint.setColor(Color.BLUE); 

        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint); 

        mPaint.setColor(Color.YELLOW); 

        mPaint.setTextSize(30); 

        String text = String.valueOf(mCount); 

        mPaint.getTextBounds(text, 0, text.length(), mBounds); 

        float textWidth = mBounds.width(); 

        float textHeight = mBounds.height(); 

        canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2 

                + textHeight / 2, mPaint); 

    } 


    @Override 

    public void onClick(View v) { 

        mCount++; 

        invalidate(); 

    } 


注意这里先是调用了getTextBounds()方法来获取到文字的宽度和高度,然后调用了drawText()方法去进行绘制就可以了。

2,组合控件

组合控件的意思就是,我们并不需要自己去绘制视图上显示的内容,而只是用系统原生的控件就好了,但我们可以将几个系统原生的控件组合到一起,这样创建出的控件就被称为组合控件

需要注意,自定义的View在使用的时候一定要写出完整的包名,不然系统将无法找到这个View。

新建一个title.xml布局文件,代码如下所示:

<?xml version="1.0" encoding="utf-8"?> 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 

    android:layout_width="match_parent" 

    android:layout_height="50dp" 

    android:background="#ffcb05" > 


    <Button 

        android:id="@+id/button_left" 

        android:layout_width="60dp" 

        android:layout_height="40dp" 

        android:layout_centerVertical="true" 

        android:layout_marginLeft="5dp" 

        android:background="@drawable/back_button" 

        android:text="Back" 

        android:textColor="#fff" /> 


    <TextView 

        android:id="@+id/title_text" 

        android:layout_width="wrap_content" 

        android:layout_height="wrap_content" 

        android:layout_centerInParent="true" 

        android:text="This is Title" 

        android:textColor="#fff" 

        android:textSize="20sp" /> 


</RelativeLayout> 

接下来创建一个TitleView继承自FrameLayout,代码如下所示:

[java] view plain copy 在CODE上查看代码片派生到我的代码片

public class TitleView extends FrameLayout { 


    private Button leftButton; 


    private TextView titleText; 


    public TitleView(Context context, AttributeSet attrs) { 

        super(context, attrs); 

        LayoutInflater.from(context).inflate(R.layout.title, this); 

        titleText = (TextView) findViewById(R.id.title_text); 

        leftButton = (Button) findViewById(R.id.button_left); 

        leftButton.setOnClickListener(new OnClickListener() { 

            @Override 

            public void onClick(View v) { 

                ((Activity) getContext()).finish(); 

            } 

        }); 

    } 


    public void setTitleText(String text) { 

        titleText.setText(text); 

    } 


    public void setLeftButtonText(String text) { 

        leftButton.setText(text); 

    } 


    public void setLeftButtonListener(OnClickListener l) { 

        leftButton.setOnClickListener(l); 

    } 


到了这里,一个自定义的标题栏就完成了,那么下面又到了如何引用这个自定义View的部分,如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 

    xmlns:tools="http://schemas.android.com/tools" 

    android:layout_width="match_parent" 

    android:layout_height="match_parent" > 


    <com.example.customview.TitleView 

        android:id="@+id/title_view" 

        android:layout_width="match_parent" 

        android:layout_height="wrap_content" > 

    </com.example.customview.TitleView> 


</RelativeLayout> 

3,继承控件

继承控件的意思就是,我们并不需要自己重头去实现一个控件,只需要去继承一个现有的控件,然后在这个控件上增加一些新的功能,就可以形成一个自定义的控件了

这种自定义控件的特点就是不仅能够按照我们的需求加入相应的功能,还可以保留原生控件的所有功能

ListView中pointToPosition()方法使用示例. * 依据触摸点的坐标计算出点击的是ListView的哪个Item

ListView的getFirstVisiblePosition等方法返回的是哪个对象

int firstPosition = lisView.getFirstVisiblePosition();

int lastPosition = lisView.getLastVisiblePosition();

int childCount = lisView.getChildCount();

boolean delResult = lv_list.removeHeaderView(header);

以上三行代码是listView的三个方法

我一直疑惑这三个方法的返回值的含义是什么,和Adapter的关系,现在用举例来解释:

listView的adapter返回的getCount = 100;

listView中第一个可见的item为2,最后一个为13

那么 :

firstPosition = 2;

lastPosition = 13;

childCount = 12;

此时给这个ListView添加 2 个Header

依然把listView滚动到第一个可见的item为2,最后一个为12

那么此时:

firstPosition = 4;

lastPosition = 15;

childCount = 12;

childCount返回的永远是当前屏幕显示的View个数,如果Header被滑动上去,那么这个Count中就没有Header的总数

且:

只有当HeaderView可见时,才会被删除,delResult才会为true。

由此可见,这三个方法不是针对Adapter中的View,而是针对包含Header在内的所有View的值。切记!

参考:http://blog.csdn.net/guolin_blog/article/details/17357967

http://blog.csdn.net/pdskyzcc1/article/details/50326629

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容