[Android] FragmentPagerAdapter 和 FragmentStatePageAdapter区别



This version of the pager is best for use when there are a handful of typically more static fragments to be paged through, such as a set of tabs. The fragment of each page the user visits will be kept in memory, though its view hierarchy may be destroyed when not visible. This can result in using a significant amount of memory since fragment instances can hold on to an arbitrary amount of state. For larger sets of pages, consider FragmentStatePagerAdapter.


This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.


1. 适用对象:

FragmentPagerAdapter适用static and less count数目Fragment

FragmentStatePageAdapter适用dynamic and more count数目Fragment

2. Fragment生命周期



3. 内存占用




[Android] TextView中setPadding()方法失效,不起作用



// does not work
tv.setPadding(20, 20, 20, 20);

// works
tv.setPadding(20, 20, 20, 20);


[Android]RecyclerView显示不出来,没有调用onCreateViewHolder(), onBindViewHolder()等

RecyclerView作为Android比较新的控件,本意是要取代ListView,其中的最大特点是可扩展性高,比ListView灵活性好,在使用的过程中会遇到一些莫名奇妙的问题,特别是刚开始使用,会碰到一些“坑”,比如标题所述,使用RecyclerView,发现RecyclerView没有显示出来,调试发现没用调用onCreateViewHolder(), onBindViewHolder()函数,甚至对应的Adapter构造函数也没调到。


1. Adapter的getItemCount() 函数返回0,这个是最“愚蠢”的原因

2. RecyclerView没有调用下列函数

setLayoutManager(new LinearLayoutManager(this))

3. RecyclerView放在ScrollView中,这个原因比较隐藏,对应的解决方案除了不要放在ScrollView之中外,还有就是更新Android Support Library 到23.2,即在build.gradle中:

dependencies {
    compile 'com.android.support:appcompat-v7:23.2.+'
    compile 'com.android.support:cardview-v7:23.2.+'


[Android] 判断ListView是否滑到顶部


private boolean listIsAtTop()   {   
    if(listView.getChildCount() == 0) return true;
    return listView.getChildAt(0).getTop() == 0;




[Android] ListView有Header时的position情况

经常需要在ListView前面加个Header View,这时获取ListView的position会有问题。



public void onItemClick(AdapterView<?> parent, View view, int position,long id)


方法1,position减去 listView.getHeaderViewsCount().


方法2,在onItemClick不要直接使用我们声明的adapter,而是用ListView里的那个decorated adapter。



3. 如果 listview 调用了一次 addHeaderView,则




会以 headerView 为第0个view,item 的 pos会从1开始。

Understanding Android’s LayoutInflater.inflate()


It’s easy to get comfortable with boilerplate setup code, so much so that we gloss over the finer details. I’ve experienced this with LayoutInflater (which coverts an XML layout file into corresponding ViewGroups and Widgets) and the way it inflates Views inside Fragment’s onCreateView() method. Upon looking for clarification in Google documentation and discussion on the rest of the web, I noticed that many others were not only unsure of the specifics of LayoutInflater’s inflate() method, but were completely misusing it.

Much of the confusion may come from Google’s vague documentation in regards to attachToRoot, the optional third parameter of the inflate() method.

Whether the inflated hierarchy should be attached to the root parameter? If false, root is only used to create the correct subclass of LayoutParams for the root view of the XML.

Maybe the confusion comes from a statement that ends in a question mark?


The general gist is this: If attachToRoot is set to true, then the layout file specified in the first parameter is inflated and attached to the ViewGroup specified in the second parameter.

Then the method returns this combined View, with the ViewGroup as the root. When attachToRoot is false, the layout file from the first parameter is inflated and returned as a View. The root of the returned View would simply be the root specified in the layout file. In either case, the ViewGroup’s LayoutParams are needed to correctly size and position the View created from the layout file.

Passing in true for attachToRoot results in a layout file’s inflated View being added to the ViewGroup right on the spot. Passing in false for attachToRoot means that the View created from the layout file will get added to the ViewGroup in some other way.

Let’s break down both scenarios with plenty of examples so we can better understand.

attachToRoot Set to True

Imagine we specified a button in an XML layout file with its layout width and layout height set to match_parent.

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

We now want to programmatically add this Button to a LinearLayout inside of a Fragment or Activity. If our LinearLayout is already a member variable, mLinearLayout, we can simply add the button with the following:

inflater.inflate(R.layout.custom_button, mLinearLayout, true);

We specified that we want to inflate the Button from its layout resource file; we then tell the LayoutInflater that we want to attach it to mLinearLayout. Our layout parameters are honored because we know the Button gets added to a LinearLayout. The Button’s layout params type should be LinearLayout.LayoutParams.

The following would also be equivalent. LayoutInflater’s two parameter inflate() method automatically sets attachToRoot to true for us.

inflater.inflate(R.layout.custom_button, mLinearLayout);

Another appropriate use of passing true for attachToRoot is a custom View. Let’s look at an example where a layout file uses a <merge> tag for its root. Using a <merge> tag signifies that the layout file allows for flexibility in terms of the type of root ViewGroup it may have.

public class MyCustomView extends LinearLayout {
    private void init() {
        LayoutInflater inflater = LayoutInflater.from(getContext());
        inflater.inflate(R.layout.view_with_merge_tag, this);

This is a perfect use for a true attachToRoot parameter. The layout file does not have a root ViewGroup in this example, so we specify our custom LinearLayout to be its root. If our layout file had a FrameLayout as its root instead of <merge>, the FrameLayout and its children would inflate as normal. Then the FrameLayout and children would get added to the LinearLayout, leaving the LinearLayout as the root ViewGroup containing the FrameLayout and children.

attachToRoot Set to False

Let’s take a look at when you would want to set attachToRoot to false. In this scenario, the View specified in the first parameter of inflate() is not attached to the ViewGroup in the second parameter at this point in time.

Recall our Button example from earlier, where we want to attach a custom Button from a layout file to mLinearLayout. We can still attach our Button to mLinearLayout by passing in false for attachToRoot—we just manually add it ourselves afterward.

Button button = (Button) inflater.inflate(R.layout.custom_button, mLinearLayout, false);

These two lines of code are equivalent to what we wrote earlier in one line of code when we passed in true for attachToRoot. By passing in false, we say that we do not want to attach our View to the root ViewGroup just yet. We are saying that it will happen at some other point in time. In this example, the other point in time is simply the addView() method used immediately below inflation.

The false attachToRoot example requires a bit more work when we manually add the View to a ViewGroup. Adding our Button to our LinearLayout was more convenient with one line of code when attachToRoot was true. Let’s look at some scenarios that absolutely require attachToRoot to be false.

A RecyclerView’s children should be inflated with attachToRoot passed in as false. The child views are inflated in onCreateViewHolder().

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    LayoutInflater inflater = LayoutInflater.from(getActivity());
    View view = inflater.inflate(android.R.layout.list_item_recyclerView, parent, false);
    return new ViewHolder(view);

RecyclerViews, not us, are responsible for determining when to inflate and present its child Views. The attachToRoot parameter should be false anytime we are not responsible for adding a View to a ViewGroup.

When inflating and returning a Fragment’s View in onCreateView(), be sure to pass in false for attachToRoot. If you pass in true, you will get an IllegalStateException because the specified child already has a parent. You should have specified where your Fragment’s view will be placed back in your Activity. It is the FragmentManager’s job to add, remove and replace Fragments.

FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.root_viewGroup);

if (fragment == null) {
    fragment = new MainFragment();
        .add(R.id.root_viewGroup, fragment)

The root_viewGroup container that will hold your Fragment in your Activity is the ViewGroup parameter given to you in onCreateView() in your Fragment. It’s also the ViewGroup you pass into LayoutInflater.inflate(). The FragmentManager will handle attaching your Fragment’s View to this ViewGroup, however. You do not want to attach it twice. Set attachToRoot to false.

public View onCreateView(LayoutInflater inflater, ViewGroup parentViewGroup, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_layout, parentViewGroup, false);
    return view;

Why are we given our Fragment’s parent ViewGroup in the first place if we don’t want to attach it in onCreateView()? Why does the inflate() method request a root ViewGroup?

It turns out that even when we are not immediately adding our newly inflated View to its parent ViewGroup, we should still use the parent’s LayoutParams in order for the new View to determine its size and position whenever it is eventually attached.

You are bound to run into some poor advice about LayoutInflater on the web. Some people will advise you to pass in null for the root ViewGroup if you are going to pass in false for attachToRoot. However, if the parent is available, you should pass it in.

FrameLayout Root

Lint will now warn you not to pass in null for root. Your app won’t crash in this scenario, but it can misbehave. When your child View doesn’t know the correct LayoutParams for its root ViewGroup, it will try to determine them on its own using generateDefaultLayoutParams.

These default LayoutParams might not be what you desired. The LayoutParams that you specified in XML will get ignored. We might have specified that our child View should match the width of our parent View, but ended up with our parent View wrapping its own content and ending up much smaller than we expected.

There are a few scenarios in which you will not have a root ViewGroup to pass into inflate(). When creating a custom View for an AlertDialog, you do not yet have access to its parent.

AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext);
View customView = inflater.inflate(R.layout.custom_alert_dialog, null);

In this case, it is okay to pass in null for the root ViewGroup. It turns out that the AlertDialog would override any LayoutParams to match_parent anyway. However, the general rule of thumb is to pass in the parent if you’re able to do so.

Avoiding Crashes, Misbehaviors and Misunderstandings

Hopefully this post helps you avoid crashes, misbehaviors and misunderstandings when using LayoutInflater. Here are some key takeaways for different uses in certain circumstances:

  • If you have a parent to pass into the root ViewGroup parameter, do so.
  • Try to avoid passing in null for the root ViewGroup.
  • Pass in false for the attachToRoot parameter when we are not the ones responsible for attaching our layout file’s View to its root ViewGroup.
  • Do not pass in true for a View that has already been attached to a ViewGroup.
  • Custom Views are a good use case to pass in true for attachToRoot.

Android Grid Layout


Question which android developers ask them-self every day — which layout to use?

It has been a while since GridLayout released — New Layout Widgets: Space and GridLayout.

Current situation in android development world regarding GridLayout is following:

  • Most of android developers don’t even know such layout exist.
  • Some android developers know about GridLayout but for some reason do not use this layout.
  • Only few android developers spent time to play with GridLayout and are actively using it.

The reason I wrote this article, is because I think this layout has been unfairly forgotten.

Why do we need Grid Layout?

GridLayout gives you possibility to create grid based layouts with a single root view.

I can use nested LinearLayout to create grid!

Yes, but you can have performance problems in hierarchies that are too deep.

I can use RelativeLayout to create grid!

Yes, but RelativeLayout has some limitations, like:

  • inability to control alignment along both axes simultaneously.
  • widgets can get off the screen / overlap when they take more space than you expected because you can’t use weigh, etc.

In other words RelativeLayout — is not flexible and responsive enough.


Let’s implement a simple layout which consist of one big image, two small icons and text next to those icons.



It’s pretty easy to implement this layout via RelativeLayout. Key attributes here are layout_below, layout_toRightOf and layout_alignTop.


At first glance everything seems to be perfect, until you start testing your layout with different text sizes.

Issue 1 inability to control alignment along both axes simultaneously.

Single line text should be vertically centered with icons, unfortunatelyRelativeLayout doesn’t offer such possibility.


Issue 2 widgets overlapping

Multi-line text cause overlapping, since text is aligned to icon vialayout_alignTop attribute.



As you can see on image below GridLayout produces much better results:

  • text is vertically aligned to icon.
  • multi-line text push widgets down.


So how to achieve such results? First define GridLayout as a root layout. Next count how many columns do you have and defineandroid:columnCount attribute, in this example we have 2 columns.

When you define views inside GridLayout they are placed one after another, so it’s not necessary to explicitly define view row and column.

If you want to stretch view for 2 or more rows / columns you can uselayout_columnSpan / layout_rowSpan attribute.

And the most important thing to remember — if you want your view to use all available space, don’t set width of your view to match_parent, set it to0dp along with layout_gravity=”fill” attribute.



GridLayout can be a powerful tool in the right hands. It provides great flexibility and performance. On the other hand it requires some time learn how it works, you usually need more time to develop and maintain such layout.


  • flexibility
  • single root layout


  • learning curve
  • maintenance





  1. 清除按钮在输入框中有内容时出现
  2. 清除按钮必须出现在输入框内
  3. 点击清除按钮,清除输入框中的所有内容
  4. 清除按钮的颜色必须与主题一致


实现第二点,我们需要使用compound drawable作为清除按钮,然后在 OnTouch listener中处理点击事件。



public class ClearableEditText extends AppCompatEditText
implements View.OnTouchListener, View.OnFocusChangeListener, TextWatcher {


public ClearableEditText(final Context context) {

public ClearableEditText(final Context context, final AttributeSet attrs) {
    super(context, attrs);

public ClearableEditText(final Context context, final AttributeSet attrs, final int defStyleAttr) {
    super(context, attrs, defStyleAttr);


  • 创建drawable,并为其加入Touch、Focus事件处理
  • 加入TextChangedListener,监听EditText内容变化
private void init(final Context context) {
    final Drawable drawable = ContextCompat.getDrawable(context, R.drawable.abc_ic_clear_mtrl_alpha);
    final Drawable wrappedDrawable = DrawableCompat.wrap(drawable); //Wrap the drawable so that it can be tinted pre Lollipop
    DrawableCompat.setTint(wrappedDrawable, getCurrentHintTextColor());
    mClearTextIcon = wrappedDrawable;
    mClearTextIcon.setBounds(0, 0, mClearTextIcon.getIntrinsicHeight(), mClearTextIcon.getIntrinsicHeight());


private void setClearIconVisible(final boolean visible) {
    mClearTextIcon.setVisible(visible, false);
    final Drawable[] compoundDrawables = getCompoundDrawables();
            visible ? mClearTextIcon : null,


private Drawable mClearTextIcon;
private OnFocusChangeListener mOnFocusChangeListener;
private OnTouchListener mOnTouchListener;

public void setOnFocusChangeListener(final OnFocusChangeListener onFocusChangeListener) {
    mOnFocusChangeListener = onFocusChangeListener;

public void setOnTouchListener(final OnTouchListener onTouchListener) {
    mOnTouchListener = onTouchListener;


最后我们来实现3个Listener,先来看focus Listener

public void onFocusChange(final View view, final boolean hasFocus) {
    if (hasFocus) {
        setClearIconVisible(getText().length() > 0);
    } else {
    if (mOnFocusChangeListener != null) {
        mOnFocusChangeListener.onFocusChange(view, hasFocus);



public boolean onTouch(final View view, final MotionEvent motionEvent) {
    final int x = (int) motionEvent.getX();
    if (mClearTextIcon.isVisible() && x > getWidth() - getPaddingRight() - mClearTextIcon.getIntrinsicWidth()) {
        if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
        return true;
    return mOnTouchListener != null && mOnTouchListener.onTouch(view, motionEvent);



public final void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
    if (isFocused()) {
        setClearIconVisible(s.length() > 0);

public void beforeTextChanged(CharSequence s, int start, int count, int after) {

public void afterTextChanged(Editable s) {



public class ClearableAutoCompleteTextView extends AppCompatAutoCompleteTextView
implements View.OnTouchListener, View.OnFocusChangeListener, TextWatcher {

该控件的源码已上传到Github: ClearableEditText

本文译自:Giving your Edit Texts the All Clear

Yelp app是如何使用Glide优化图片加载的




Glide原理的核心是为bitmap维护一个对象池。对象池的主要目的是通过减少大对象的分配以重用来提高性能(至于对象池的概览,可以查看 这个Android performance pattern 视频)。

Dalvik和ART虚拟机都没有使用compacting garbage collector,compacting garbage collector是一种模式,这种模式中GC会遍历堆,同时把活跃对象移到相邻内存区域,让更大的内存块可以用在后续的分配中。因为安卓没有这种模式,就可能会出现被分配的对象分散在各处,对象之间只有很小的内存可用。如果应用试图分配一个大于邻近的闲置内存块空间的对象,就会导致OutOfMemoryError,然后崩溃,即使总的空余内存空间大于对象的大小。

使用对象池还可以帮助提高滚动的性能,因为重用bitmap意味着更少的对象被创建与回收。垃圾回收会导致“停止一切(Stop The World)”事件,这个事件指的是回收器执行期间,所有线程(包括UI线程)都会暂停。这个时候,图像帧无法被渲染同时UI可能会停滞,这在滚动期间尤其明显。

Yelp app是如何使用Glide优化图片加载的


Glide使用起来很简单,而且不需要任何特别的配置就自动包含了bitmap pooling 。

DrawableRequestBuilder requestBuilder = Glide.with(context).load(imageUrl);

这就是加载一张图片的全部要求。就像安卓中的很多地方一样,with() 方法中的context到底是哪种类型是不清楚的。有一点很重要需要记住,就是传入的context类型影响到Glide加载图片的优化程度,Glide可以监视activity的生命周期,在activity销毁的时候自动取消等待中的请求。但是如果你使用Application context,你就失去了这种优化效果。




类似的是,如果相关的item已经滚出了屏幕的范围,Glide会自动取消列表中的悬着的图片请求 。因为绝大多数开发者都会在adapter中利用view的回收,Glide做到这点是通过在ImageView上设置一个tag,在加载另外一张图片之前检查这个tag,如果存在就取消第一次请求。

Glide提供了几个让你感觉图片加载速度变快的特性。第一个就是在图片显示在屏幕上之前就预先取出图片。它提供了一个ListPreloader类, 它被应该事先取出的item数目实例化。然后通过setOnScrollListener(OnScrollListener).被传递给ListView。你想在ListView之外也能预先取出图片吗?没问题,使用前面的builder对象就可以了,只需调用builder.downloadOnly()。

downloadOnly见:https://github.com/bumptech/glide/wiki/Loading-and-Caching-on-Background-Threads 。


Android中深入理解 LayoutInflater.inflate()








假设我们在XML layout文件中写了一个Button并指定了宽高为match_parent。

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


inflater.inflate(R.layout.custom_button, mLinearLayout, true);



inflater.inflate(R.layout.custom_button, mLinearLayout);


public class MyCustomView extends LinearLayout {
    private void init() {
    LayoutInflater inflater = LayoutInflater.from(getContext());
    inflater.inflate(R.layout.view_with_merge_tag, this);





Button button = (Button) inflater.inflate(R.layout.custom_button, mLinearLayout, false);




public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    LayoutInflater inflater = LayoutInflater.from(getActivity());
    View view = inflater.inflate(android.R.layout.list_item_recyclerView, parent, false);
    return new ViewHolder(view);



FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.root_viewGroup);

if (fragment == null) {
    fragment = new MainFragment();
    fragmentManager.beginTransaction().add(R.id.root_viewGroup, fragment).commit();


public View onCreateView(LayoutInflater inflater, ViewGroup parentViewGroup, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_layout, parentViewGroup, false);
    return view;








AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext);
View customView = inflater.inflate(R.layout.custom_alert_dialog, null);

在这种情况下,可以将null作为root ViewGroup传入。后来我发现AlertDialog还是会重写LayoutParams并设置各项参数为match_parent。但是,规则还是在有ViewGroup可以传入时传入它。



  • 如果可以传入ViewGroup作为根元素,那就传入它。
  • 避免将null作为根ViewGroup传入。
  • 当我们不负责将layout文件的View添加进ViewGroup时设置attachToRoot参数为false。
  • 不要在View已经被添加进ViewGroup时传入true。
  • 自定义View时很适合将attachToRoot设置为true。