Showing posts with label layout. Show all posts
Showing posts with label layout. Show all posts

Monday, February 13, 2017

Constraint Layout beta5 lint error

Constraint Layout beta 5 is the release candidate, and added a lint to deprecate older versions.

The easiest way to get rid of the lint error is to press Alt-Enter and choose the first option to upgrade.

However, I encountered a bug, which forces me to stay with beta4 until the next version comes out with the fix. I still want to get rid of the lint error so my continuous build does not fail. I didn't want to change lintOptions abortOnError to false in build.gradle because I still want my build to catch other fatal lint errors.

I tried to get Android Studio to fix this lint error for me by choosing Disable inspection from Alt-Enter. Alas, that only changed the local settings. Turns out that I need to go to Settings → Editor → Inspections to undo that.

The option I wanted is Suppress: Add tools:ignore="MissingConstraints" attribute, which modifies the xml for me to add the appropriate lint suppression.

<android.support.constraint.ConstraintLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:ignore="MissingConstraints">
<!-- Cannot upgrade to beta5 due to http://b.android.com/233863 -->

Finally, I added a comment to remind me why I suppress the lint error.

More info: Suppress Lint Warnings.

Saturday, October 8, 2016

Constraint Layout: Icon Label Text

I am making one of those classic layouts: an icon with two lines of text. I would like to use vector drawable for the icon, and scale it according to the text sizes. I want the top edge of the icon to line up with the top edge of the first line of text, and the bottom edge of the icon to line up with the bottom edge of the second line.

How would I do that? With ConstraintLayout!

<ImageView
  android:id="@+id/flower_image"
  android:layout_width="0dp"
  android:layout_height="0dp"
  app:layout_constraintTop_toTopOf="@+id/label"
  app:layout_constraintBottom_toBottomOf="@+id/text"
  app:layout_constraintLeft_toLeftOf="parent"
  app:layout_constraintDimensionRatio="1:1"
  app:srcCompat="@drawable/ic_flower"/>
<TextView
  android:id="@+id/label"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  app:layout_constraintLeft_toRightOf="@+id/image"
  app:layout_constraintTop_toTopOf="parent"
  android:text="@string/flower"
  android:textSize="16sp"/>
<TextView
  android:id="@+id/text"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  app:layout_constraintTop_toBottomOf="@+id/label"
  app:layout_constraintLeft_toLeftOf="@+id/label"
  android:text="@string/jasmine"
  android:textSize="24sp"/>

The width and the height of the ImageView is 0dp. This tells ConstraintLayout to compute them by the constraints. In this case, the height of the ImageView is determined by these constraints:

app:layout_constraintTop_toTopOf="@+id/label"
app:layout_constraintBottom_toBottomOf="@+id/text"

The width is the same as the height.

app:layout_constraintDimensionRatio="1:1"

The rest of the constraints are for positioning.

With that, the image scales up as the text sizes increase. It stays sharp because it is a vector.

Layout Editor

I tried to make this layout with the Layout Editor, but could not figure out how to create the constraint app:layout_constraintTop_toTopOf="@+id/label" for the ImageView. I was hovering my cursor around the top edge but not sure how to drag it to link the two views. So I added up playing with the editor a bit to deduce the XML attributes, and switched to editing the XML directly. I hope to use the layout editor in my next attempt to use Constraint Layout.

Follow-up Twitter discussion:

Read the whole Twitter thread.

Source code

github.com/chiuki/iconlabeltext

Click on either TextView to increase its size. Click on the image to reset.

Monday, January 19, 2015

Partial SlidingPaneLayout

Gmail has an interesting UI on tablet:

The side pane is always visible, showing icons when collapsed, cross fading to more details when expanded. How is it implemented?

My first observation is that the main pane slides when the side pane expands, so I know it is not a NavigationDrawer. Let's try a SlidingPaneLayout.

SlidingPaneLayout

<android.support.v4.widget.SlidingPaneLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sliding_pane_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
  <TextView
      android:layout_width="240dp"
      android:layout_height="match_parent"
      android:background="@color/blue"
      android:text="@string/pane_1"/>
  <TextView
      android:layout_width="400dp"
      android:layout_height="match_parent"
      android:layout_weight="1"
      android:background="@color/light_blue"
      android:text="@string/pane_2"/>
</android.support.v4.widget.SlidingPaneLayout>

Looks good, except the main pane turns gray. Fortunately we can change the fade color to transparent.

SlidingPaneLayout layout = (SlidingPaneLayout) 
    findViewById(R.id.sliding_pane_layout);
layout.setSliderFadeColor(Color.TRANSPARENT);

Partial side pane

Now I want to make the side pane partially visible when collapsed. Took me a while (plus a shower) to figure that out, but once I did it was really simple: add margin to the main pane.

<android.support.v4.widget.SlidingPaneLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sliding_pane_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
  <TextView
      android:layout_width="240dp"
      android:layout_height="match_parent"
      android:background="@color/blue"
      android:text="@string/pane_1"/>
  <TextView
      android:layout_width="400dp"
      android:layout_height="match_parent"
      android:layout_weight="1"
      android:layout_marginLeft="64dp"
      android:background="@color/light_blue"
      android:text="@string/pane_2"/>
</android.support.v4.widget.SlidingPaneLayout>

With the margin, the side pane peeks from below when collapsed.

Cross fade

Finally, the cross fade. I replaced the side pane with FrameLayout, the bottom view being the full pane and the top view being the partial pane.

<com.sqisland.android.CrossFadeSlidingPaneLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sliding_pane_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
  <FrameLayout
      android:layout_width="240dp"
      android:layout_height="match_parent"
      android:background="@color/purple">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/full"/>
    <TextView
        android:layout_width="64dp"
        android:layout_height="match_parent"
        android:background="@color/blue"
        android:text="@string/partial"/>
  </FrameLayout>
  <TextView
      android:layout_width="400dp"
      android:layout_height="match_parent"
      android:layout_weight="1"
      android:layout_marginLeft="64dp"
      android:background="@color/light_blue"
      android:text="@string/pane_2"/>
</com.sqisland.android.CrossFadeSlidingPaneLayout>

Then I subclass SlidingPaneLayout to cross fade between the partial pane and the full pane on slide. To do that, I need to get the two panes.

@Override
protected void onFinishInflate() {
  super.onFinishInflate();

  if (getChildCount() < 1) {
    return;
  }

  View panel = getChildAt(0);
  if (!(panel instanceof ViewGroup)) {
    return;
  }

  ViewGroup viewGroup = (ViewGroup) panel;
  if (viewGroup.getChildCount() != 2) {
    return;
  }
  fullView = viewGroup.getChildAt(0);
  partialView = viewGroup.getChildAt(1);

  super.setPanelSlideListener(crossFadeListener);
}

Since SlidingPaneLayout already has the convention of specifying the side pane vs main pane by position, I also look for the partial pane and full pane by position. The first child of the SlidingPaneLayout is the side pane, its first child is the full pane, second child is the partial pane. I stash them in the fields fullView and partialView, which are used in the cross-fade listener.

private SimplePanelSlideListener crossFadeListener 
    = new SimplePanelSlideListener() {
  @Override
  public void onPanelSlide(View panel, float slideOffset) {
    super.onPanelSlide(panel, slideOffset);
    if (partialView == null || fullView == null) {
      return;
    }

    partialView.setVisibility(isOpen() ? View.GONE : VISIBLE);
    partialView.setAlpha(1 - slideOffset);
    fullView.setAlpha(slideOffset);
  }
};

Here, I change the alpha of the partial pane and the full pane depending on the slide offset. Since I don't want the partial pane to react to touch events when the layout is open, I set the partial pane to View.GONE. The same logic needs to be applied onLayout because devices with sufficient width (e.g. tablets) may start with the layout opened.

@Override
protected void onLayout(
    boolean changed, int l, int t, int r, int b) {
  super.onLayout(changed, l, t, r, b);

  if (partialView != null) {
    partialView.setVisibility(isOpen() ? View.GONE : VISIBLE);
  }
}

Here we go, a partially shown side pane that cross fades into a different view when expanded. Enjoy!

Source: https://github.com/chiuki/sliding-pane-layout

Monday, January 9, 2012

Android: Square View

In Monkey Write, the character sits in a square. Initially I just hard-coded the width to be equal to the height, but I wanted them to scale with the device dimensions. Today I found an elegant way to do it, with 3 lines of code in my custom View:

public void onMeasure(int widthSpec, int heightSpec) {
  super.onMeasure(widthSpec, heightSpec);
  int size = Math.min(getMeasuredWidth(), getMeasuredHeight());
  setMeasuredDimension(size, size);
}

Since my class extends View, I can reuse all the measurement logic by calling super. After that, I take the measured width and height, find the smaller one, and use it to set the width to be the same as the height.

I made a sample project to test the SquareView. It is set to take one third of the screen width, and the easiest way to try it with different dimensions is to rotate the screen.

Source code: http://github.com/chiuki/android-square-view.

Wednesday, December 21, 2011

Android Layout 101

Sarah Allen invited me to teach Android classes at Blazing Cloud, and we kicked off last night with Android Layout 101.



Presenting: Android Layout 101

We started with the Hello World app, gradually introducing more and more layout types and attributes. From time to time I showed a screenshot and asked everyone to modify the xml file to achieve the same layout.



"hmm... how I do get the buttons to go to the second line?"

By the end of the workshop we were implementing this screen:

This can be done with either LinearLayout or RelativeLayout. The LinearLayout version uses a nested horizontal LinearLayout to specify the second row, with android:gravity="right" to scoot the buttons over. One of the students, Anne, went on to experiment with setting android:layout_gravity="right" on the buttons themselves, but that did not work. We were quite baffled.

Turns out android:layout_gravity="right" is only honored by a vertical LinearLayout, not horizontal one. I guess the LinearLayout expects to put down one View after another horizontally, so if it puts the first button to the right, the second button would have nowhere to go. As a result it simply ignores the layout_gravity attributes. Still, it was rather confusing, and it is precisely the kind of nuance you only find out when you get your hands dirty.

The screenshot exercises gave everyone a chance to put theory into practice, and see how the elements interact with each other. You can try them as well. Just go to http://www.sqisland.com/talks/android-layout-101. Even if you are already familiar with the basic layout components, you may still want to check out puzzles at the end. Some of the solutions may surprise you!