com.btmura.android.reddit.app.AbstractBrowserActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.btmura.android.reddit.app.AbstractBrowserActivity.java

Source

/*
 * Copyright (C) 2012 Brian Muramatsu
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.btmura.android.reddit.app;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.StrictMode;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.widget.DrawerLayout;
import android.util.DisplayMetrics;
import android.view.MenuItem;
import android.view.View;

import com.btmura.android.reddit.BuildConfig;
import com.btmura.android.reddit.R;
import com.btmura.android.reddit.util.Objects;
import com.btmura.android.reddit.widget.ThingView;

abstract class AbstractBrowserActivity extends GlobalMenuActivity
        implements OnNavigationEventListener, OnSubredditSelectedListener, OnThingSelectedListener,
        OnThingEventListener, OnBackStackChangedListener, AccountNameHolder, SubredditHolder, ThingHolder {

    private static final String TAG_CONTROL_FRAGMENT = "control";
    private static final String TAG_LEFT_FRAGMENT = "left";
    private static final String TAG_RIGHT_FRAGMENT = "right";
    private static final String TAG_THING_FRAGMENT = "thing";

    private static final int ANIMATION_OPEN_NAV = 0;
    private static final int ANIMATION_CLOSE_NAV = 1;
    private static final int ANIMATION_EXPAND_LEFT = 2;
    private static final int ANIMATION_COLLAPSE_LEFT = 3;
    private static final int ANIMATION_EXPAND_RIGHT = 4;
    private static final int ANIMATION_COLLAPSE_RIGHT = 5;

    interface LeftFragment extends ComparableFragment {
    }

    interface RightFragment extends ComparableFragment {
        void setSelectedThing(String thingId, String linkId);
    }

    private final Class<? extends Activity> thingActivityClass;

    protected ActionBar bar;
    protected boolean isSinglePane;
    private boolean isSingleChoice;

    private View navContainer;
    private View leftContainer;
    private View rightContainer;
    private View thingContainer;
    private int leftWidth;
    private int thingBodyWidth;

    private int fullNavWidth;
    private int durationMs;

    private AnimatorSet openNavAnimator;
    private AnimatorSet closeNavAnimator;
    private AnimatorSet expandLeftAnimator;
    private AnimatorSet collapseLeftAnimator;
    private AnimatorSet expandRightAnimator;
    private AnimatorSet collapseRightAnimator;

    private String accountName;
    private int filter;
    protected DrawerLayout drawerLayout;

    AbstractBrowserActivity(Class<? extends Activity> thingActivityClass) {
        this.thingActivityClass = thingActivityClass;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (BuildConfig.DEBUG) {
            StrictMode.enableDefaults();
        }
        super.onCreate(savedInstanceState);
        setContentView();
        setupPrereqs();
        if (!skipSetup(savedInstanceState)) {
            setupCommonFragments(savedInstanceState);
            setupCommonViews();
            doSetup(savedInstanceState);
        }
    }

    protected abstract void setContentView();

    private void setupPrereqs() {
        bar = getActionBar();
        drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        thingContainer = findViewById(R.id.thing_container);
        isSinglePane = thingContainer == null;
        isSingleChoice = !isSinglePane;
    }

    protected abstract boolean skipSetup(Bundle savedInstanceState);

    private void setupCommonFragments(Bundle savedInstanceState) {
        if (savedInstanceState == null) {
            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            ft.add(GlobalMenuFragment.newInstance(), GlobalMenuFragment.TAG);
            ft.commit();
        }
    }

    private void setupCommonViews() {
        if (!isSinglePane) {
            getSupportFragmentManager().addOnBackStackChangedListener(this);

            navContainer = findViewById(R.id.nav_container);
            leftContainer = findViewById(R.id.left_container);
            rightContainer = findViewById(R.id.right_container);

            Resources r = getResources();
            leftWidth = r.getDimensionPixelSize(R.dimen.left_width);
            fullNavWidth = r.getDisplayMetrics().widthPixels;
        }

        measureThingBodyWidth();
    }

    protected abstract void doSetup(Bundle savedInstanceState);

    protected abstract boolean hasLeftFragment();

    // Methods used to setup the initial fragments.

    protected void setBrowserFragments(String requestedSubreddit, ThingBundle requestedThingBundle) {
        int containerId = drawerLayout != null ? R.id.drawer_container : R.id.left_container;
        setLeftFragment(containerId, ControlFragment.newDrawerInstance(),
                NavigationFragment.newInstance(requestedSubreddit, requestedThingBundle));
    }

    protected void setSearchThingsFragments(String accountName, String subreddit, String query, int filter) {
        selectAccountWithFilter(accountName, filter);
        setCenterFragment(R.id.right_container,
                ControlFragment.newSearchThingsInstance(accountName, subreddit, query, filter),
                SearchThingListFragment.newInstance(accountName, subreddit, query, filter, isSingleChoice), false);
    }

    protected void setUserProfileFragments(String accountName, String profileUser, int filter) {
        selectAccountWithFilter(accountName, filter);
        setCenterFragment(R.id.right_container,
                ControlFragment.newUserProfileInstance(accountName, profileUser, filter),
                ProfileThingListFragment.newInstance(accountName, profileUser, filter, isSingleChoice), false);
    }

    protected void setSidebarFragments(String accountName, String subreddit) {
        selectAccountWithFilter(accountName, 0);
        setCenterFragment(R.id.right_container, ControlFragment.newSidebarInstance(accountName, subreddit),
                SidebarFragment.newInstance(accountName, subreddit), false);
    }

    protected void setSearchSubredditsFragments(String accountName, String query, int filter) {
        selectAccountWithFilter(accountName, filter);
        if (isSinglePane) {
            setCenterFragment(R.id.right_container,
                    ControlFragment.newSearchSubredditsInstance(accountName, query, filter),
                    SearchSubredditListFragment.newInstance(accountName, query, isSingleChoice), false);
        } else {
            setLeftFragment(R.id.left_container,
                    ControlFragment.newSearchSubredditsInstance(accountName, query, filter),
                    SearchSubredditListFragment.newInstance(accountName, query, isSingleChoice));
        }
    }

    protected void setRelatedSubredditsFragments(String accountName, String subreddit) {
        selectAccountWithFilter(accountName, 0);
        if (isSinglePane) {
            setCenterFragment(R.id.right_container,
                    ControlFragment.newRelatedSubredditsInstance(accountName, subreddit),
                    RelatedSubredditListFragment.newInstance(accountName, subreddit, isSingleChoice), false);
        } else {
            setLeftFragment(R.id.left_container,
                    ControlFragment.newRelatedSubredditsInstance(accountName, subreddit),
                    RelatedSubredditListFragment.newInstance(accountName, subreddit, isSingleChoice));
        }
    }

    // Callbacks triggered by calling one of the initial methods that select fragments.

    @Override
    public void onNavigationSubredditSelected(String accountName, String subreddit, boolean isRandom, int filter,
            ThingBundle thingBundle, boolean force) {
        selectAccountWithFilter(accountName, filter);
        ControlFragment controlFrag = ControlFragment.newSubredditInstance(accountName, subreddit, isRandom,
                filter);
        setRightFragment(R.id.right_container, controlFrag,
                SubredditThingListFragment.newInstance(accountName, subreddit, filter, isSingleChoice), force);
        if (thingBundle != null) {
            setThingFragment(controlFrag.withThingBundle(thingBundle));
        }
    }

    @Override
    public void onNavigationProfileSelected(String accountName, int filter, boolean force) {
        selectAccountWithFilter(accountName, filter);
        setRightFragment(R.id.right_container, ControlFragment.newProfileInstance(accountName, filter),
                ProfileThingListFragment.newInstance(accountName, accountName, filter, isSingleChoice), force);
    }

    @Override
    public void onNavigationSavedSelected(String accountName, int filter, boolean force) {
        selectAccountWithFilter(accountName, filter);
        setRightFragment(R.id.right_container, ControlFragment.newSavedInstance(accountName, filter),
                ProfileThingListFragment.newInstance(accountName, accountName, filter, isSingleChoice), force);
    }

    @Override
    public void onNavigationMessagesSelected(String accountName, int filter, boolean force) {
        selectAccountWithFilter(accountName, filter);
        setRightFragment(R.id.right_container, ControlFragment.newMessagesInstance(accountName, filter),
                MessageThingListFragment.newInstance(accountName, accountName, filter, isSingleChoice), force);
    }

    @Override
    public void onSubredditSelected(View view, String subreddit, boolean onLoad) {
        if (!onLoad || !isSinglePane) {
            selectSubreddit(view, subreddit, !onLoad);
        }
    }

    @Override
    public void onThingSelected(View view, ThingBundle thingBundle, int pageType) {
        selectThing(view, thingBundle);
    }

    @Override
    public int onThingBodyMeasure() {
        return thingBodyWidth;
    }

    // Method to set state in this activity.

    private void selectAccountWithFilter(String accountName, int filter) {
        this.accountName = accountName;
        this.filter = filter;
        if (drawerLayout != null) {
            drawerLayout.closeDrawers();
        }
    }

    // Methods to select a subreddit

    private void selectSubreddit(View view, String subreddit, boolean force) {
        if (isSinglePane) {
            selectSubredditSinglePane(view, subreddit);
        } else {
            selectSubredditMultiPane(subreddit, force);
        }
    }

    private void selectSubredditSinglePane(View view, String subreddit) {
        Intent intent = new Intent(this, BrowserActivity.class);
        intent.putExtra(BrowserActivity.EXTRA_SUBREDDIT, subreddit);
        launchActivity(view, intent);
    }

    private void selectSubredditMultiPane(String subreddit, boolean force) {
        setRightFragment(R.id.right_container, getControlFragment().withSubreddit(subreddit),
                SubredditThingListFragment.newInstance(accountName, subreddit, filter, isSingleChoice), force);
    }

    // Methods to select a thing

    private void selectThing(View view, ThingBundle thingBundle) {
        if (isSinglePane) {
            selectThingSinglePane(view, thingBundle);
        } else {
            selectThingMultiPane(thingBundle);
        }
    }

    protected void selectThingSinglePane(View view, ThingBundle thingBundle) {
        Intent intent = new Intent(this, thingActivityClass);
        intent.putExtra(ThingActivity.EXTRA_THING_BUNDLE, thingBundle);
        launchActivity(view, intent);
    }

    private void selectThingMultiPane(ThingBundle thingBundle) {
        setThingFragment(getControlFragment().withThingBundle(thingBundle));
    }

    private void setThingFragment(ControlFragment controlFrag) {
        safePopBackStackImmediate();

        ThingFragment frag = ThingFragment.newInstance(accountName, controlFrag.getThingBundle());
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.add(controlFrag, TAG_CONTROL_FRAGMENT);
        ft.replace(R.id.thing_container, frag, TAG_THING_FRAGMENT);
        ft.addToBackStack(null);
        ft.commitAllowingStateLoss();
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void launchActivity(View view, Intent intent) {
        Bundle options = ThingView.makeActivityOptions(view);
        if (options != null) {
            startActivity(intent, options);
        } else {
            startActivity(intent);
        }
    }

    @Override
    public void onThingTitleDiscovery(String title) {
    }

    // Methods for setting the fragments on the screen.

    private <F extends Fragment & LeftFragment> void setLeftFragment(int containerId, ControlFragment controlFrag,
            F frag) {
        if (!Objects.equals(frag, getLeftFragment())) {
            safePopBackStackImmediate();

            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            ft.add(controlFrag, TAG_CONTROL_FRAGMENT);
            ft.replace(containerId, frag, TAG_LEFT_FRAGMENT);
            removeFragment(ft, TAG_RIGHT_FRAGMENT);
            ft.setTransition(
                    FragmentTransaction.TRANSIT_FRAGMENT_OPEN | FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
            ft.commitAllowingStateLoss();

            refreshActionBar(null);
            refreshViews(null);
        }
    }

    private <F extends Fragment & RightFragment> void setCenterFragment(int containerId,
            ControlFragment controlFrag, F centerFrag, boolean force) {
        setRightFragmentRemoveLeft(containerId, controlFrag, centerFrag, force, true);
    }

    private <F extends Fragment & RightFragment> void setRightFragment(int containerId, ControlFragment controlFrag,
            F rightFrag, boolean force) {
        setRightFragmentRemoveLeft(containerId, controlFrag, rightFrag, force, false);
    }

    private <F extends Fragment & RightFragment> void setRightFragmentRemoveLeft(int containerId,
            ControlFragment controlFrag, F rightFrag, boolean force, boolean removeLeft) {
        if (force || !Objects.equals(rightFrag, getRightFragment())) {
            safePopBackStackImmediate();

            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            ft.add(controlFrag, TAG_CONTROL_FRAGMENT);
            if (removeLeft) {
                removeFragment(ft, TAG_LEFT_FRAGMENT);
            }
            ft.replace(containerId, rightFrag, TAG_RIGHT_FRAGMENT);
            removeFragment(ft, TAG_THING_FRAGMENT);
            ft.setTransition(
                    FragmentTransaction.TRANSIT_FRAGMENT_OPEN | FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
            ft.commitAllowingStateLoss();

            refreshActionBar(controlFrag);
            refreshViews(controlFrag.getThingBundle());
        }
    }

    private void safePopBackStackImmediate() {
        FragmentManager fm = getSupportFragmentManager();
        if (fm.getBackStackEntryCount() > 0) {
            fm.removeOnBackStackChangedListener(this);
            fm.popBackStackImmediate();
            fm.addOnBackStackChangedListener(this);
        }
    }

    private void removeFragment(FragmentTransaction ft, String tag) {
        Fragment f = getSupportFragmentManager().findFragmentByTag(tag);
        if (f != null) {
            ft.remove(f);
        }
    }

    @Override
    public void onBackStackChanged() {
        ControlFragment cf = getControlFragment();
        if (cf != null) {
            refreshActionBar(cf);
            refreshViews(cf.getThingBundle());
            refreshCheckedItems();
        }
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        if (savedInstanceState != null) {
            ControlFragment cf = getControlFragment();
            if (cf != null) {
                refreshActionBar(cf);
                refreshViews(cf.getThingBundle());
                refreshCheckedItems();
            }
        }
    }

    protected abstract void refreshActionBar(ControlFragment controlFrag);

    private void refreshViews(ThingBundle thingBundle) {
        measureThingBodyWidth();
        if (!isSinglePane) {
            updateVisibility(thingBundle);
        }
    }

    private void measureThingBodyWidth() {
        if (isSinglePane || drawerLayout != null && navContainer != null) {
            return;
        }
        Resources r = getResources();
        DisplayMetrics dm = r.getDisplayMetrics();
        int padding = r.getDimensionPixelSize(R.dimen.element_padding);
        if (navContainer != null) {
            int newWidth = hasLeftFragment() ? leftWidth : 0;
            thingBodyWidth = dm.widthPixels - newWidth - padding * 2;
        } else {
            thingBodyWidth = dm.widthPixels / 5 * 2 - padding * 3;
        }
    }

    private void updateVisibility(ThingBundle thingBundle) {
        boolean hasLeftFragment = hasLeftFragment();
        boolean hasThing = thingBundle != null;
        if (navContainer != null) {
            // Refresh the subreddit list container which is inside the
            // navigation container.
            if (leftContainer != null) {
                leftContainer.setVisibility(getVisibility(hasLeftFragment));
            }
            int cnv = navContainer.getVisibility();
            int nnv = getVisibility(!hasThing);
            if (cnv != nnv) {
                if (isVisible(nnv)) {
                    startAnimation(ANIMATION_OPEN_NAV);
                } else {
                    startAnimation(ANIMATION_CLOSE_NAV);
                }
            }
        } else {
            int ctv = thingContainer.getVisibility();
            int ntv = getVisibility(hasThing);
            if (ctv != ntv) {
                if (isVisible(ntv)) {
                    startAnimation(hasLeftFragment ? ANIMATION_COLLAPSE_LEFT : ANIMATION_COLLAPSE_RIGHT);
                } else {
                    startAnimation(hasLeftFragment ? ANIMATION_EXPAND_LEFT : ANIMATION_EXPAND_RIGHT);
                }
            } else if (leftContainer != null) {
                // Refresh the subreddit list container if the animations don't take
                // care of it.
                leftContainer.setVisibility(getVisibility(hasLeftFragment && !hasThing));
            }
        }
    }

    private int getVisibility(boolean visible) {
        return visible ? View.VISIBLE : View.GONE;
    }

    private boolean isVisible(int visibility) {
        return visibility == View.VISIBLE;
    }

    private void refreshCheckedItems() {
        if (!isSinglePane) {
            RightFragment rf = getRightFragment();
            if (rf != null) {
                ControlFragment cf = getControlFragment();
                ThingBundle thingBundle = cf.getThingBundle();
                if (thingBundle != null) {
                    rf.setSelectedThing(thingBundle.getThingId(), thingBundle.getLinkId());
                } else {
                    rf.setSelectedThing(null, null);
                }
            }
        }
    }

    @Override
    public String getAccountName() {
        return accountName;
    }

    @Override
    public String getSubreddit() {
        ControlFragment cf = getControlFragment();
        if (cf != null) {
            ThingBundle thingBundle = cf.getThingBundle();
            if (thingBundle != null) {
                String subreddit = thingBundle.getSubreddit();
                if (subreddit != null) {
                    return subreddit;
                }
            }

            String subreddit = cf.getSubreddit();
            if (subreddit != null) {
                return subreddit;
            }
        }
        return null;
    }

    @Override
    public boolean isShowingThing() {
        ControlFragment cf = getControlFragment();
        return cf != null && cf.getThingBundle() != null;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case android.R.id.home:
            handleHome(item);
            return true;

        default:
            return super.onOptionsItemSelected(item);
        }
    }

    private void handleHome(MenuItem item) {
        FragmentManager fm = getSupportFragmentManager();
        if (fm.getBackStackEntryCount() > 0) {
            fm.popBackStack();
        } else if ((bar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
            finish();
        }
    }

    protected boolean hasThing() {
        return getThingFragment() != null;
    }

    protected ControlFragment getControlFragment() {
        return (ControlFragment) getSupportFragmentManager().findFragmentByTag(TAG_CONTROL_FRAGMENT);
    }

    private LeftFragment getLeftFragment() {
        return (LeftFragment) getSupportFragmentManager().findFragmentByTag(TAG_LEFT_FRAGMENT);
    }

    private RightFragment getRightFragment() {
        return (RightFragment) getSupportFragmentManager().findFragmentByTag(TAG_RIGHT_FRAGMENT);
    }

    protected NavigationFragment getNavigationFragment() {
        return (NavigationFragment) getSupportFragmentManager().findFragmentByTag(TAG_LEFT_FRAGMENT);
    }

    private ThingFragment getThingFragment() {
        return (ThingFragment) getSupportFragmentManager().findFragmentByTag(TAG_THING_FRAGMENT);
    }

    private void startAnimation(int type) {
        getAnimator(type).start();
    }

    private AnimatorSet getAnimator(int type) {
        switch (type) {
        case ANIMATION_OPEN_NAV:
            if (openNavAnimator == null) {
                openNavAnimator = newOpenNavAnimator();
            }
            return openNavAnimator;

        case ANIMATION_CLOSE_NAV:
            if (closeNavAnimator == null) {
                closeNavAnimator = newCloseNavAnimator();
            }
            return closeNavAnimator;

        case ANIMATION_EXPAND_LEFT:
            if (expandLeftAnimator == null) {
                expandLeftAnimator = newExpandLeftAnimator();
            }
            return expandLeftAnimator;

        case ANIMATION_COLLAPSE_LEFT:
            if (collapseLeftAnimator == null) {
                collapseLeftAnimator = newCollapseLeftAnimator();
            }
            return collapseLeftAnimator;

        case ANIMATION_EXPAND_RIGHT:
            if (expandRightAnimator == null) {
                expandRightAnimator = newExpandRightAnimator();
            }
            return expandRightAnimator;

        case ANIMATION_COLLAPSE_RIGHT:
            if (collapseRightAnimator == null) {
                collapseRightAnimator = newCollapseRightAnimator();
            }
            return collapseRightAnimator;

        default:
            throw new IllegalArgumentException();
        }
    }

    private AnimatorSet newOpenNavAnimator() {
        ObjectAnimator ncTransX = ObjectAnimator.ofFloat(navContainer, "translationX", -fullNavWidth, 0);
        ObjectAnimator tpTransX = ObjectAnimator.ofFloat(thingContainer, "translationX", 0, fullNavWidth);

        AnimatorSet as = new AnimatorSet();
        as.setDuration(durationMs).play(ncTransX).with(tpTransX);
        as.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                navContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                navContainer.setVisibility(View.VISIBLE);
                thingContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                thingContainer.setVisibility(View.VISIBLE);
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                navContainer.setLayerType(View.LAYER_TYPE_NONE, null);
                thingContainer.setLayerType(View.LAYER_TYPE_NONE, null);
                thingContainer.setVisibility(View.GONE);
            }
        });
        return as;
    }

    private AnimatorSet newCloseNavAnimator() {
        ObjectAnimator ncTransX = ObjectAnimator.ofFloat(navContainer, "translationX", 0, -leftWidth);

        AnimatorSet as = new AnimatorSet();
        as.setDuration(durationMs).play(ncTransX);
        as.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                navContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                navContainer.setVisibility(View.VISIBLE);
                thingContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                thingContainer.setVisibility(View.GONE);
                thingContainer.setTranslationX(0);
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                navContainer.setLayerType(View.LAYER_TYPE_NONE, null);
                navContainer.setVisibility(View.GONE);
                thingContainer.setLayerType(View.LAYER_TYPE_NONE, null);
                thingContainer.setVisibility(View.VISIBLE);
            }
        });
        return as;
    }

    private AnimatorSet newCollapseLeftAnimator() {
        ObjectAnimator slTransX = ObjectAnimator.ofFloat(leftContainer, "translationX", 0, -leftWidth);
        ObjectAnimator tlTransX = ObjectAnimator.ofFloat(rightContainer, "translationX", 0, -leftWidth);

        AnimatorSet as = new AnimatorSet();
        as.setDuration(durationMs).play(slTransX).with(tlTransX);
        as.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                leftContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                leftContainer.setVisibility(View.VISIBLE);
                rightContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                rightContainer.setVisibility(View.VISIBLE);
                thingContainer.setVisibility(View.GONE);
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                leftContainer.setLayerType(View.LAYER_TYPE_NONE, null);
                leftContainer.setVisibility(View.GONE);
                rightContainer.setLayerType(View.LAYER_TYPE_NONE, null);
                rightContainer.setTranslationX(0);
                thingContainer.setVisibility(View.VISIBLE);
            }
        });
        return as;
    }

    private AnimatorSet newExpandLeftAnimator() {
        ObjectAnimator slTransX = ObjectAnimator.ofFloat(leftContainer, "translationX", -leftWidth, 0);
        ObjectAnimator tlTransX = ObjectAnimator.ofFloat(rightContainer, "translationX", -leftWidth, 0);

        AnimatorSet as = new AnimatorSet();
        as.setDuration(durationMs).play(slTransX).with(tlTransX);
        as.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                leftContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                leftContainer.setVisibility(View.VISIBLE);
                rightContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                rightContainer.setVisibility(View.VISIBLE);
                thingContainer.setVisibility(View.GONE);
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                leftContainer.setLayerType(View.LAYER_TYPE_NONE, null);
                rightContainer.setLayerType(View.LAYER_TYPE_NONE, null);
            }
        });
        return as;
    }

    private AnimatorSet newCollapseRightAnimator() {
        ObjectAnimator tcTransX = ObjectAnimator.ofFloat(thingContainer, "translationX", leftWidth, 0);

        AnimatorSet as = new AnimatorSet();
        as.setDuration(durationMs).play(tcTransX);
        as.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                if (leftContainer != null) {
                    leftContainer.setVisibility(View.GONE);
                }
                rightContainer.setVisibility(View.VISIBLE);
                thingContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                thingContainer.setVisibility(View.VISIBLE);
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                thingContainer.setLayerType(View.LAYER_TYPE_NONE, null);
            }
        });
        return as;
    }

    private AnimatorSet newExpandRightAnimator() {
        ObjectAnimator tcTransX = ObjectAnimator.ofFloat(thingContainer, "translationX", 0, leftWidth);

        AnimatorSet as = new AnimatorSet();
        as.setDuration(durationMs).play(tcTransX);
        as.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                if (leftContainer != null) {
                    leftContainer.setVisibility(View.GONE);
                }
                rightContainer.setVisibility(View.VISIBLE);
                thingContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                thingContainer.setVisibility(View.GONE);
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                thingContainer.setLayerType(View.LAYER_TYPE_NONE, null);
            }
        });
        return as;
    }
}