Tag: android

Android MotionLayout Tutorial – Collapsing View

Android MotionLayout Tutorial – Collapsing View

MotionLayout is a layout class that extends from ConstraintLayout. MotionLayout has all the features of ConstraintLayout. On top of that, it also provides the ability to easily animate changes to your UI, without needing to know much about UI interactions and the Android Animation Frameworks.

Take for example this subtle animation of a view being scrolled and the profile picture shrinking. Before MotionLayout, this would be a tedious task to complete. We may have needed a CollapsingToolbar and some other custom animation code to ensure the profile picture scales correctly. Now with MotionLayout, this is really easy to achieve with one extra XML file.

In this article, we will be looking at implementing a simple swipe action on a RecyclerView and how we can achieve the scaling animation with MotionLayout.

Add MotionLayout as a Gradle Dependency

To get started with MotionLayout, you need to make sure we have the latest version in your build.gradlefile. (Note: MotionLayout is still in alpha at the time of writing)

 implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3'

Create your layout XML as per usual

The great part about MotionLayout is that it uses the same constructs as ConstraintLayout. Everything you’ve previously learnt about ConstraintLayout (ie barriers, chains etc) is applicable to layouts that we build with MotionLayout.

To get started, open up your editor and change the root element of your layout to use MotionLayout. Add a RecyclerView and an ImageView to your layout. Make sure the RecyclerView is constrained to the bottom of the ImageView.

RecyclerView with ImageView above

The XML behind the following layout should look similar to this:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout 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:orientation="vertical"                                android:layout_width="match_parent"                                     app:showPaths="false"                          app:layoutDescription="@xml/motion_layout_example"                                android:layout_height="match_parent">
    <View android:layout_width="0dp" android:layout_height="0dp"
          app:layout_constraintStart_toStartOf="parent"
          android:background="@color/colorPrimary"
          android:id="@+id/background"
          app:layout_constraintBottom_toBottomOf="@id/space"
          app:layout_constraintTop_toTopOf="parent"
          app:layout_constraintEnd_toEndOf="parent" android:alpha="0"
    />

    <ImageView
            android:layout_width="140dp"
            android:layout_height="0dp"
            android:scaleType="centerCrop"
            android:id="@+id/imageViewAvatar"
            android:layout_marginTop="16dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="16dp"
            app:layout_constraintDimensionRatio="h,1:1"
            app:srcCompat="@drawable/veruca"/>


    <TextView
            android:text="@string/veruca_salt_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:id="@+id/textViewName"
            android:fontFamily="@font/willywonka"
            app:layout_constraintStart_toEndOf="@+id/imageViewAvatar"
            android:layout_marginStart="16dp"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="16dp"
            android:textAppearance="@style/TextAppearance.AppCompat.Display1"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toTopOf="@+id/imageViewAvatar" 
            android:layout_marginBottom="8dp"
            app:layout_constraintBottom_toTopOf="@+id/space"/>

    <androidx.recyclerview.widget.RecyclerView
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp"
            app:layout_constraintBottom_toBottomOf="parent"
            tools:listitem="@layout/list_item_status"
            android:id="@+id/recyclerViewStatus" android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/imageViewAvatar">

    </androidx.recyclerview.widget.RecyclerView>
    <Space
            android:layout_width="0dp"
            android:layout_height="8dp"
            android:id="@+id/space"
            app:layout_constraintStart_toEndOf="@+id/imageViewAvatar"
            app:layout_constraintTop_toBottomOf="@id/imageViewAvatar"
            app:layout_constraintEnd_toEndOf="@+id/imageViewAvatar"
            app:layout_constraintStart_toStartOf="@+id/imageViewAvatar"/>
</androidx.constraintlayout.motion.widget.MotionLayout>

Create the MotionScene XML

In order to animate this layout we need to describe how views should animate in the layout. To do this, create an XML file in the xml folder of your application. We will call it motion_scene.xml.

The first thing that we will do, is define the Transition for this MotionScene. We set the reference to the start and end ConstraintSets on the Transition object. We can also set the duration of the transition.

<?xml version="1.0" encoding="utf-8"?>
<MotionScene
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:android="http://schemas.android.com/apk/res/android">

    <Transition
            app:constraintSetStart="@id/start"
            app:constraintSetEnd="@id/end"
            app:duration="1000">
        <OnSwipe
                app:touchAnchorId="@+id/recyclerViewStatus"
                app:touchAnchorSide="top"
                app:dragDirection="dragUp" />
    </Transition>
    <ConstraintSet android:id="@+id/start">
    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
    </ConstraintSet>
</MotionScene>

The next step, that can be seen in the code snippet above, is to create the OnSwipe declaration. This indicates to the MotionLayout that it should monitor the layout for a swipe movement. When the user performs a dragUp gesture on the specified touchAnchorId, the MotionScene will start interpolating between the two states defined (start and end). In this case, it is the recyclerViewStatus view that it will be monitoring for the dragUp gesture.

In the code snippet above, you may notice that there are two ConstraintSet tags defined in the MotionScene. These tags are for the start and end constraints of the view. The beautiful part about MotionLayout is that it will automatically interpolate between these two states and produce some pretty magical animations. At the moment, we don’t have any constraint changes applied, so let’s change that.

The first view we want to animate is the ImageView. We want the size of the view to decrease as a user scrolls up on the RecyclerView. To do that, we will add an end Constraint to the ImageView to adjust the width and height to 40dp .

<?xml version="1.0" encoding="utf-8"?>
<MotionScene
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:android="http://schemas.android.com/apk/res/android">

    <Transition
            app:constraintSetStart="@id/start"
            app:constraintSetEnd="@id/end"
            app:duration="1000">
        <OnSwipe
                app:touchAnchorId="@+id/recyclerViewStatus"
                app:touchAnchorSide="top"
                app:dragDirection="dragUp" />
    </Transition>

    <ConstraintSet android:id="@+id/start">

    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        
        <Constraint android:id="@id/imageViewAvatar"
                    android:layout_width="40dp"
                    android:layout_height="40dp" android:layout_marginTop="16dp"
                    app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
                    android:layout_marginStart="16dp">
        </Constraint>

    </ConstraintSet>
</MotionScene>

Linking the MotionScene to the Layout

In order to link the MotionScene to the Layout, you will need to set the property app:layoutDescription="@xml/motion_layout_example" to point to your newly created XML file on the root MotionLayout element.

Running this on device, you will observe that as you scroll up on the RecyclerView , the ImageView decreases in size.

Animating Visibility of a View

Now if you wanted to animate the background to go from invisible to visible, we can just add a Property animation to the start and end constraints. The final XML will look as follows:

<?xml version="1.0" encoding="utf-8"?>
<MotionScene
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:android="http://schemas.android.com/apk/res/android">

    <Transition
            app:constraintSetStart="@id/start"
            app:constraintSetEnd="@id/end"
            app:duration="1000">
        <OnSwipe
                app:touchAnchorId="@+id/recyclerViewStatus"
                app:touchAnchorSide="top"
                app:dragDirection="dragUp" />
    </Transition>

    <ConstraintSet android:id="@+id/start">

        <Constraint android:id="@id/background">
            <PropertySet app:alpha="0"/>
        </Constraint>
    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">

        <Constraint android:id="@id/imageViewAvatar"
                    android:layout_width="40dp"
                    android:layout_height="40dp"
                    android:layout_marginTop="16dp"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    android:layout_marginStart="16dp">
        </Constraint>

        <Constraint android:id="@id/background">
            <PropertySet app:alpha="1"/>
        </Constraint>
    </ConstraintSet>
</MotionScene>

It’s a wrap!

As you can see from the short example above, using MotionLayout can decrease the amount of code you need to write in order to achieve delightful animations. In the next few posts, we will be covering more of the features of MotionLayout, as this article has just touched the surface. Till next time!

Follow me on Twitter – @riggaroo.

Android ConstraintLayout 2.0: ConstraintLayoutStates

Android ConstraintLayout 2.0: ConstraintLayoutStates

With the introduction of ConstraintLayout 2.0, there is an interesting new feature called ConstraintLayoutStates. ConstraintLayoutStates allow you to create a layout with different states and switch between them easily. Typically, most layouts contain a loading state, initial state, end state and error state. Using ConstraintLayoutStates, there is a clean way to switch between these different states.

How do you use this new feature? Glad you asked!

Step 1: Create your different states in separate XML files

Create the different state layout files that your layout needs. Each layout must include the same views, they can just have different properties such as their visibility, or their constraints.

ConstraintLayoutStates example layouts
3 layouts for the different layout states.

Step 2: Create a ConstraintLayoutStates XML file

In the xml resource folder, create an xml file containing your states. In this example, we will call it constraint_layout_states_example.xml. Inside this file place all the different representations of your layout. Give them meaningful ids such as startloading etc. and then link them to the relevant constraint files.

<?xml version="1.0" encoding="utf-8"?>
<ConstraintLayoutStates xmlns:android="http://schemas.android.com/apk/res/android"
                        xmlns:app="http://schemas.android.com/apk/res-auto">
    <State
            android:id="@+id/start"
            app:constraints="@layout/activity_cl_states_start"/>
    <State
            android:id="@+id/loading"
            app:constraints="@layout/activity_cl_states_loading"/>
    <State
            android:id="@+id/end"
            app:constraints="@layout/activity_cl_states_end"/>
</ConstraintLayoutStates>

 

Step 3: Switch between the states

In your activity/fragment, you can now easily switch between these states based on different conditions. You will first need to load the state description using loadLayoutDescription() on your ConstraintLayout object. Once you have done that, you can call constraintLayout.setState() with any of the states that you have defined in the previous states file.

In the example below, we are setting the state to the loading state. Then after some time (in this example I am just posting a delayed runnable, but this could easily be a network call that returns at a later stage), we are setting it to the end state.

class ConstraintLayoutStatesExampleActivity : AppCompatActivity() {

    val handler = Handler()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_cl_states_start)
        stateConstraintLayout.loadLayoutDescription(R.xml.constraint_layout_states_example)
        var changed = false
        buttonBakeCake.setOnClickListener {
            stateConstraintLayout.setState(R.id.loading, 0, 0)
            postDelayed(handler,  {
                stateConstraintLayout.setState(if (changed) R.id.start else R.id.end,0, 0)
                changed = !changed
            }, null, 3000L)
        }
    }
}

The above example running on a device:ConstraintLayoutStates example

That’s it! You should now be able to use ConstraintLayoutStates in your own apps. This example can be found on Github here.

Enjoyed this post? Let me know your thoughts on Twitter!

ConstraintLayout 2.0: ImageFilterView

ConstraintLayout 2.0: ImageFilterView

Whilst browsing through the various examples online with the new ConstraintLayout 2.0, I stumbled upon ImageFilterView. This got my attention immediately and I decided to investigate further.

An ImageFilterView allows you to perform some common image filtering techniques on an ImageView, including saturation, contrast, warmth and crossfade. 

If you have tried to implement these image filters before, you may have run into ColorMatrix. If you look at the source of ImageFilterView you will see that these methods have just been nicely wrapped up with simpler API usage.

For example, if you would like to adjust the warmth, contrast or saturation, all you need to do is set a property on the ImageFilterView:

<androidx.constraintlayout.utils.widget.ImageFilterView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:id="@+id/imageView"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        app:warmth="1.2"
        app:contrast="1.0"
        app:saturation="2.0"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintDimensionRatio="16:9"
        tools:srcCompat="@tools:sample/avatars"
        />

You can also access this programmatically, so you could add a SeekBar to control these values.

seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
    override fun onProgressChanged(seekbar: SeekBar?, progress: Int, p2: Boolean) {
        val percentage = (progress / 100.0f)
        imageView.saturation = (percentage) + 1
    }

    override fun onStartTrackingTouch(seekbar: SeekBar?) {

    }

    override fun onStopTrackingTouch(seekbar: SeekBar?) {

    }
})

There is also the ability to crossfade between two different images using the crossfade method defined on ImageFilterView. This allows you to merge two images together.

If you are looking for a quick way to add some basic image effects, ImageFilterView is definitely something to consider. It is fast to use and execute since it is backed by ColorMatrix which uses the GPU (and not the CPU) to process the resultant image.

Here is an example of ImageFilterView in action:

Realtime Image Processing with ImageFilterView
Realtime Image Processing with ImageFilterView

 

The downside to using this approach is that you are not in full control of the exact pixel values that are going to be used, which could be problematic if you are developing an image editing application.

Overall, I’m really excited about the ImageFilterView class! I hope it is the start of some awesome Image effects offered by the Android Team.

Check out the ConstraintLayout demo repository for the code used in the above example.

Follow me on Twitter for more.

 

Android ConstraintLayout — Build a layout from Scratch + Q&A — Sunday (26 Nov) 5PM GMT+2 [Live Stream]

Android ConstraintLayout — Build a layout from Scratch + Q&A — Sunday (26 Nov) 5PM GMT+2 [Live Stream]

I’ve seen quite a lot of misuse/misunderstanding of how to use ConstraintLayout practically. I was contemplating the best way to talk about these common “Don’ts” that I see people do.

I’ve decided to try do something a bit different than a blog post. I’ll be live streaming on Sunday, 26 November at 5PM GMT+2 (that is today 😄 ) on YouTube and Twitch.

Join me and ask questions whilst I code this layout below. I’ll also cover some common mistakes that I’ve seen people make when using ConstraintLayout.

Follow me on YouTubeTwitch and Twitter to get more updates!

Hope to see you there

ConstraintLayout Android Example
ConstraintLayout Android Example

Android Accessibility – Making your app Switch Access Compatible

Android Accessibility – Making your app Switch Access Compatible

I received an email a few days ago, where I had a request for the Book Dash Android app to support “Switch Access”. To be honest, I had no idea what this email meant. My first thought was, “Hey, this will never run on a Nintendo Switch” but I realised that this probably wasn’t what they were requesting. After doing a bit more research on what exactly Switch Access is in Android, I realised there isn’t a lot of information around the Android Accessibility feature. Hopefully after reading through this blog post you will be interested in making sure your app is Switch Access compatible.

What is Accessibility?

Accessibility refers to the design of products, devices, services, or environments for people who experience disabilities (ref). For example, some people may be visually impaired and use a service like TalkBack on their device, which speaks out everything on their screen. Some people may enlarge the size of the font of their phone so they can read it clearly. Whilst others might suffer from muscle conditions that prevent them from being able to use the touch screen. This is where Switch Access is meant to help.

What is Android Switch Access?

Switch Access allows users with limited dexterity to use their Android device without using the touch screen. Users typically would use a device similar to the one pictured below to navigate the user interface of their phone.

Switch Access Android
Typical Switch Device

Some switches have one button, others have two buttons or more. Switch Access can be configured to use a keyboard, a device like the one pictured above or built-in buttons on your device such as the volume up and volume down key.

Here is a video explaining how it works:

Great, we’ve now seen how it works, lets try use it on our app.

Enabling Switch Access for Testing

In order to test our apps with Switch Access, we need to enable it. In order to enable it you need to do the following:

  1. Open Settings on your device.
  2. Open the “Accessibility” section.
  3. There should be a service listed called “Switch Access”. Select this service. Before enabling the service, click on the “Settings” for the service. Click “Assign Switches for Scanning”. Here is where you will map certain key events to “Next” and “Select” action.

    Switch Access Settings
    Switch Access Settings
  4. Click “Select”, and then press the key you want to map to the “Select” function. I pressed the “Volume Down” button. Repeat this for the “Next” action, I used the “Volume Up” for the “Next” action but you can use any key you wish.

    Switch Access Android - Assign action to button
    Switch Access Assign Action to Button
  5. Once you have done this, click back and enable “Switch Access”.

We can now navigate through our phone using the volume up and down buttons.

How do I make my app Switch Access compatible?

This is the part that was a mystery that I was battling to solve myself. No amount of Googling got me the result I was looking for, so hopefully my insights can help you achieve compatibility for your apps.

TL;DR: Make sure views that are clickable are selectable and reachable by a keyboard. Make sure views that require gestures can also be properly navigated to without using the required gesture.

Unfortunately there is no “enable this flag and switch access will work” solution to this problem. After using the Book Dash app with Switch Access enabled, I realised there were a few places that didn’t work well with the service enabled.

Switch Access problems in Book Dash

1. Inability to turn a page of the book

The screen that displays the book for reading is in a ViewPager, which means users have to swipe to get to the next page. This was intuitive for me whilst using the touch screen, but not whilst using it with Switch Access. There was no way to turn the page of the book without swiping on the screen. 

The solution for my ViewPager gesture scrolling, was to add click listeners onto each page of the book to go to the next and previous page of the book. I then added “focusable” fields onto the views that had click listeners attached. The following lines were added to each page container (with the click listeners pointing to the correct logic):

            android:clickable="true"
            android:focusable="true"
            android:onClick="@{customClickListener}"

Switch Access Android - Navigate and Turn pages
Switch Access Android – Navigate and Turn pages

2. Two clickable areas on the download button

Another example in the Book Dash app was the Download Book button which had two clickable areas. Accessing it using Switch Access caused a bit of frustration, as a secondary dialog showed up asking which one to select.

Multiple clickable areas on download button - Switch Access Android
Multiple clickable areas on download button

The solution for this issue was to remove android:clickable=”true” on the outer view, as the custom view already defined a clickable attribute. Simple fix, but saves users a lot of frustration!

If you want to see more code, check out the open source Github repository for more information.

Summary

Enabling Switch Access and testing your app with it on allows you to make your app accessible for users with limited dexterity. This is vitally important if you want to make a high quality, accessible app. In the next few posts we will be looking into other small improvements to make the app more accessible.

Make sure to subscribe to this blog to follow the journey of making Book Dash more accessible. Have something to add? Let me know on Twitter.

Subscribe to Blog via Email

Enter your email address to subscribe to this blog and receive notifications of new posts by email.