home.


Tagged: android


Android: Style Toolbar/ActionBar and centre text

In recent support library updates, we have the Toolbar to use instead of the standard ActionBar to style via various properties in your style xml files.

You add this manually to your layout XML file.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  xmlns:tools="http://schemas.android.com/tools">
  <android.support.v7.widget.Toolbar
      android:id="@+id/toolbar"
      android:layout_alignParentTop="true"
      android:layout_width="match_parent"
      android:layout_height="?attr/actionBarSize"
      android:background="@color/toolbar_background"
      >
  ...

If you want to centre the title text, for example, you now just add a normal TextView and centre align it.

      <TextView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:textColor="@color/toolbar_text"
          android:gravity="center"
          tools:text="Some title"
          android:id="@+id/toolbar_text"
          style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"/>

Obviously this is a lot to put at the top of every activity, you can instead place it in a separate layout file and use an include directive:

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <include android:id="@+id/toolbar_layout"
        layout="@layout/toolbar_layout" />
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/toolbar_layout">
...

In order to give this the title of your activity, you add this code in your onStart() method, or anywhere after the layout has been inflated:

TextView toolbarText = (TextView) findViewById(R.id.toolbar_text);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
if(toolbarText!=null && toolbar!=null) {
    toolbarText.setText(getTitle());
    setSupportActionBar(toolbar);
}

This will grab our toolbar, grab the text view which will hold our title, set the text on our text view and set the toolbar as our ActionBar.

Now when options menu etc, it will appear in that toolbar.

android android-toolbar

Android: Hiding the FAB and nested scroll events in CoordinatorLayout

The CoordinatorLayout helps you deal with nested scroll events from certain children, RecyclerView in our case.

If we place a RecyclerView and a FloatingActionBar in a CoordinatorLayout, we can tell the FAB to hide when we get a nested scroll event from the RecyclerView.

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/coord"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rec_view"
        android:background="@android:color/holo_green_dark"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"
        >
    </android.support.v7.widget.RecyclerView>
    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@android:drawable/star_big_on"
        app:layout_behavior="com.example.blar.myapplication.ScrollAwareFABBehavior"
        app:layout_anchorGravity="bottom|right|end"
        />
</android.support.design.widget.CoordinatorLayout

Note the FAB has app:layout_behavior pointing to a class. Let’s look at that now.

public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior<FloatingActionButton>  {
  public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
    super();
  }

  @Override
  public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
                                     final View directTargetChild, final View target, final int nestedScrollAxes) {
    return true;
  }

  @Override
  public void onNestedScroll(final CoordinatorLayout coordinatorLayout,
      final FloatingActionButton child,
      final View target, final int dxConsumed, final int dyConsumed,
      final int dxUnconsumed, final int dyUnconsumed) {
    super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,dxUnconsumed, dyUnconsumed);
    if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
      child.hide();
    } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
      child.show();
    }
  }
}

The first onStartNestedScroll method asks if we want to respond to a scroll event that happened in the CoordinatorLayout. We’re saying yes, but usually you say only on vertical scroll or what have you.

The the next onNestedScroll method, actually either hides or shows the FAB depending on the direction of scroll.

Each method have default implementations that return false and does nothing respectively.

android android-coordinatorlayout

Bulk renaming files in Android Asset Studio download

If you use Roman Nurik’s wonderful Android Asset Studio, you’ll get a downloaded zip of differently sized icons.

You may want to rename these files from ic_launcher.png to something else - otherwise it may clash with an existing file of that name.

You can use xargs to do this. Move to the directory of the downloaded file, in the res directory and run this:

ls | xargs -I {} mv {}ic_launcher.png {}YOUR_NEW_FILENAME.png

We list each mipmap-xxxx directory, and rename all the ic_launcher.png files within to our new filename.

android unix-xargs

Android: RecyclerView with animated expandable sections

In previous posts, we’ve created a RecyclerView with section headings and a click listener. Look for posts under the android-recyclerview tag.

We continue to use our data object, SectionOrRow, that defines if the data is a section or a row. But we will change three lines in the adapter. These changes will define our list size and the list item by taking into account the hidden positions.

In getItemCount() we will get the size from SectionOrRow.getSizeExcludingHidden(mData). And in getItemViewType() and onBindViewHolder() we will get the data item by calling SectionOrRow.getUnderlyingItem(mData, position).

Outside our adapter, we will now look for clicks on the section headings. If we get one, we will check if it’s expanded or not, and if it is we will call a method that hides or shows our rows:

adapter.setClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // Get the position of the view
        int visiblePosition = YOUR_RECYCLER_VIEW.indexOfChild(v);
        // Get the item it represents
        StringSectionOrRow item = SectionOrRow.getUnderlyingItem(data, visiblePosition);
        // if the section is not hidden, hide the section, and if it is, show it.
        if(!item.isRow() && !item.getSectionHidden()) {
            int numberHidden = SectionOrRow.hideUntilNextSection(data, visiblePosition, true);
            adapter.notifyItemRangeRemoved(numberHidden+1, numberHidden);
        } else if(!item.isRow() && item.getSectionHidden()) {
            int numberShown = SectionOrRow.hideUntilNextSection(data, visiblePosition, false);
            adapter.notifyItemRangeInserted(numberShown+1, numberShown);
        }
    }
});

SectionOrRow.hideUntilNextSection(data, visiblePosition, true) will look at our list of data, and set all the rows until the next section as hidden. The last parameter defines if we’re going to set it to hidden or not.

adapter.notifyItemRangeInserted(visiblePosition+1, hidden) tells our recycler view to animate the insertion of our views, or to animate their delection. That is, when we set our underlying data to hidden, we want the recycler view to animate their removal.

Our old SectionOrRow class now has two new booleans: hidden and sectionCollapsed. These define if we should show or hide the row and if the section is collapsed.

The first new method in this class is the one used to size the list including all the hidden rows. Its implementation is straight forward.

public static int getSizeExcludingHidden(List<SectionOrRow> rows) {
    int size = 0;
    for (StringSectionOrRow r : rows) {
        if(!r.hidden) size++;
    }
    return size;
}

The second is used internally only. You give it the position which does not include all the hidden cells. The adapter will give you this value. It will then return the position including the hidden rows.

We loop over all the underlying items, and if such is not hidden we increment a visibleRow integer. Eventually this will match the position we’re looking for, and we return the underlying position:

private static int getUnderlyingRowPosition(List<StringSectionOrRow> rows, int visiblePosition) {
    int underlyingRow = 0, visibleRow = 0;
    for (; underlyingRow < rows.size(); underlyingRow++) {
        StringSectionOrRow stringSectionOrRow = rows.get(underlyingRow);
        if(!stringSectionOrRow.hidden && visibleRow==visiblePosition) break;
        if(!stringSectionOrRow.hidden) visibleRow++;
    }
    return underlyingRow;
}

The next method is the one where we get the item to display. We give it the position in the adapter that does not include all the hidden cells. And, via the above, it returns the underlying row for the adapter to display:

public static SectionOrRow getUnderlyingItem(List<StringSectionOrRow> rows, int visiblePosition) {
    return rows.get(getUnderlyingRowPosition(rows, visiblePosition));
}

Our final function is the one we use to set the rows to be hidden or shown. We give it the row objects the adapter is given, the position of our section header (as found in the above click listener) and then define if we want to either hide or show the rows:

public static int hideUntilNextSection(List<StringSectionOrRow> rows,
                                       int visiblePosition,
                                       boolean hide) {
    // What's he underlying row data
    int underlying = StringSectionOrRow.getUnderlyingRowPosition(rows, visiblePosition);
    SectionOrRow section = rows.get(underlying);
    // Say that's now collapsed (or fully visible)
    section.setSectionCollapsed(hide);
    underlying++; // Go to the next row
    // Let's now either show or hide those values
    // until we reach the next section
    int rowsHidden = 0;
    for (int i = underlying; i < rows.size();i++,rowsHidden++) {
        StringSectionOrRow r = rows.get(i);
        if(!r.isRow) break;
        else {
            r.setHidden(hide);
        }
    }
    return rowsHidden;
}

Now when the recycler view sizes itself, it takes into account the hidden cells, and when it display an item, it does the same.

When one its section heading is clicked, it checks if it’s already collapsed, if it is it sets all the rows under that section to visible, and the animates their insertion.

Equally, if the section is not collapsed, it sets all the rows to hidden, and animates their removal.

android android-recyclerview

Android: On item click listener for RecyclerView Adapter

RecyclerView.Adapter, unlike its ListView colleague, does not have a item click listener.

You can, however, use a normal View.OnClickListener and then use indexOfChild to get the position of the view in the recycler view.

Add the callback setter to your adapter:

public void setClickListener(View.OnClickListener callback) {
    mClickListener = callback;
}

And in your onCreateViewHolder set that:

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.some_layout, parent, false);
    RecyclerView.ViewHoldre holder = new SomeViewHolder(v);
    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            mClickListener.onClick(view);
        }
    });
    return holder;
}

Now, outside the adapter, you can fetch the position like so:

YOUR_RECYCLER_ADAPTER.setClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        int pos = YOUR_REYCLER_VIEW.indexOfChild(v);
        ...
    }
});

And then pos will have the index of the view.

This is better than having the position passed through in the callback, since any callback binding may have the incorrect position, if the internal position of the views change.

android android-recyclerview

Page 1 of 15
Next