Skip to content

FarshidABZ/CoordinatorBehavior

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CoordinatorBehavior

In this example I've tried to replicate the profile animation of Telegram to show how a CoordinatorLayout.Behavior could be used.

Demo

Usage

Behavior

To custom the behavior of these elements, the first work is make a subclass of CoordinatorLayout.Behavior, been T is your child class, for example: ImageView. These are methods we must override:

  • layoutDependsOn(): called every time that something happens in the layout, what we must do to return true once we identify the dependency, in the example, this method is automatically fired when the user scrolls (because the Toolbar will move), in that way we can make our child sight react accordingly.

  • onDependentViewChanged(): Called when layoutDependsOn() return true. Here is where you must to implement our animations, translations or movements always related with the provided dependency.

    Calculating the Toolbar height to placing the child view

public class ImageBehavior extends CoordinatorLayout.Behavior {
    private final Context context;

    private int startXPosition;
    private float startToolbarPosition;

    private int startYPosition;

    private int finalYPosition;
    private int finalHeight;
    private int startHeight;
    private int finalXPosition;

    public ImageBehavior(Context context, AttributeSet attrs) {
        this.context = context;
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency instanceof Toolbar;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        maybeInitProperties((ImageView) child, dependency);

        final int maxScrollDistance = (int) (startToolbarPosition - getStatusBarHeight());
        float expandedPercentageFactor = dependency.getY() / maxScrollDistance;

        float distanceYToSubtract = ((startYPosition - finalYPosition)
                * (1f - expandedPercentageFactor)) + (child.getHeight() / 2);

        float distanceXToSubtract = ((startXPosition - finalXPosition)
                * (1f - expandedPercentageFactor)) + (child.getWidth() / 2);

        float heightToSubtract = ((startHeight - finalHeight) * (1f - expandedPercentageFactor));

        child.setY(startYPosition - distanceYToSubtract);
        child.setX(startXPosition - distanceXToSubtract);

        CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
        lp.width = (int) (startHeight - heightToSubtract);
        lp.height = (int) (startHeight - heightToSubtract);
        child.setLayoutParams(lp);
        return true;
    }

    @SuppressLint("PrivateResource")
    private void maybeInitProperties(ImageView child, View dependency) {
        if (startYPosition == 0)
            startYPosition = (int) (dependency.getY());

        if (finalYPosition == 0)
            finalYPosition = (dependency.getHeight() / 2);

        if (startHeight == 0)
            startHeight = child.getHeight();

        if (finalHeight == 0)
            finalHeight = context.getResources().getDimensionPixelOffset(R.dimen.image_small_width);

        if (startXPosition == 0)
            startXPosition = (int) (child.getX() + (child.getWidth() / 2));

        if (finalXPosition == 0)
            finalXPosition = context.getResources().getDimensionPixelOffset(R.dimen.abc_action_bar_content_inset_material) + (finalHeight / 2);

        if (startToolbarPosition == 0)
            startToolbarPosition = dependency.getY() + (dependency.getHeight() / 2);
    }

    public int getStatusBarHeight() {
        int result = 0;
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");

        if (resourceId > 0) {
            result = context.getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }
}

UI

The next step is creating an Activity to test this custom Behavior. Declaring it's layout with CoordinatorLayout as root, AppBarLayout and CollapsingToolbarLayout:

<?xml version="1.0" encoding="utf-8"?>
<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/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFF">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="256dp">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing"
            android:layout_width="match_parent"
            android:layout_height="256dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

            <FrameLayout
                android:id="@+id/flTitle"
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:layout_gravity="bottom|center_horizontal"
                android:background="#FFFFFF"
                android:orientation="vertical"
                app:layout_collapseMode="parallax">

                <LinearLayout
                    android:id="@+id/llTitle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:gravity="center"
                    android:orientation="vertical">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="Header"
                        android:textColor="@android:color/black"
                        android:textSize="25sp" />

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="4dp"
                        android:text="Title"
                        android:textColor="@android:color/black" />

                </LinearLayout>
            </FrameLayout>
        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>


    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="@dimen/activity_horizontal_margin"
        android:background="#FFFFFF"
        android:scrollbars="none"
        app:behavior_overlapTop="0.1dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </android.support.v4.widget.NestedScrollView>

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="#FFFFFF"
        app:layout_anchor="@id/flTitle">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/tvToolbarTitle"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_marginLeft="56dp"
                android:gravity="center_vertical"
                android:text="Toolbar Title"
                android:textColor="@android:color/black"
                android:textSize="20sp" />

        </LinearLayout>
    </android.support.v7.widget.Toolbar>

    <ImageView
        android:id="@+id/imgAvatar"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_gravity="center"
        android:src="@color/colorAccent"
        app:layout_behavior="com.farshidabz.coordinatorbehavior.ImageBehavior" />

</android.support.design.widget.CoordinatorLayout>

Control the view

Create a class to control and handle appbar behavior:

class HandleProfileViewBehavior implements AppBarLayout.OnOffsetChangedListener {

    private static final float PERCENTAGE_TO_SHOW_TITLE_AT_TOOLBAR = 0.7f;
    private static final float PERCENTAGE_TO_HIDE_TITLE_DETAILS = 0.7f;
    private static final int ALPHA_ANIMATIONS_DURATION = 250;

    private boolean isTheTitleVisible = false;
    private boolean isTheTitleContainerVisible = true;

    private LinearLayout linearLayoutTitle;
    private TextView textViewTitle;

    HandleProfileViewBehavior(View rootView) {

        AppBarLayout appbar = (AppBarLayout) rootView.findViewById(R.id.appbar);
        linearLayoutTitle = (LinearLayout) rootView.findViewById(R.id.llTitle);
        textViewTitle = (TextView) rootView.findViewById(R.id.tvToolbarTitle);

        appbar.addOnOffsetChangedListener(this);

        startAlphaAnimation(textViewTitle, 0, View.INVISIBLE);
    }

    @Override
    public void onOffsetChanged(AppBarLayout appBarLayout, int offset) {
        int maxScroll = appBarLayout.getTotalScrollRange();
        float percentage = (float) Math.abs(offset) / (float) maxScroll;

        handleAlphaOnTitle(percentage);
        handleToolbarTitleVisibility(percentage);
    }

    private void handleToolbarTitleVisibility(float percentage) {
        if (percentage >= PERCENTAGE_TO_SHOW_TITLE_AT_TOOLBAR) {

            if (!isTheTitleVisible) {
                startAlphaAnimation(textViewTitle, ALPHA_ANIMATIONS_DURATION, View.VISIBLE);
                isTheTitleVisible = true;
            }
        } else {
            if (isTheTitleVisible) {
                startAlphaAnimation(textViewTitle, ALPHA_ANIMATIONS_DURATION, View.INVISIBLE);
                isTheTitleVisible = false;
            }
        }
    }

    private void handleAlphaOnTitle(float percentage) {
        if (percentage >= PERCENTAGE_TO_HIDE_TITLE_DETAILS) {
            if (isTheTitleContainerVisible) {
                startAlphaAnimation(linearLayoutTitle, ALPHA_ANIMATIONS_DURATION, View.INVISIBLE);
                isTheTitleContainerVisible = false;
            }
        } else {

            if (!isTheTitleContainerVisible) {
                startAlphaAnimation(linearLayoutTitle, ALPHA_ANIMATIONS_DURATION, View.VISIBLE);
                isTheTitleContainerVisible = true;
            }
        }
    }

    private static void startAlphaAnimation(View v, long duration, int visibility) {
        AlphaAnimation alphaAnimation = (visibility == View.VISIBLE)
                ? new AlphaAnimation(0f, 1f)
                : new AlphaAnimation(1f, 0f);

        alphaAnimation.setDuration(duration);
        alphaAnimation.setFillAfter(true);
        v.startAnimation(alphaAnimation);
    }
}

And finally in your Activity create an instance of HandleProfileViewBehavior.class like this:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        toolbar.setTitle("");
        setSupportActionBar(toolbar);

        HandleProfileViewBehavior handleProfileViewBehavior =
                new HandleProfileViewBehavior(findViewById(R.id.activity_main));
    }
}

About

In this example, I've tried to replicate the profile animation of Telegram to show how a CoordinatorLayout.Behavior could be used

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages