com.mishiranu.dashchan.util.DrawerToggle.java Source code

Java tutorial

Introduction

Here is the source code for com.mishiranu.dashchan.util.DrawerToggle.java

Source

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * 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.
 *
 * ********************************************************************************
 *
 * Copyright 2014-2016 Fukurou Mishiranu
 */

package com.mishiranu.dashchan.util;

import java.lang.reflect.Field;

import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.ViewDragHelper;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.mishiranu.dashchan.C;

public class DrawerToggle implements DrawerLayout.DrawerListener {
    private final Activity activity;
    private final DrawerLayout drawerLayout;

    private final ArrowDrawable arrowDrawable;
    private final SlideDrawable slideDrawable;
    private Drawable homeAsUpIndicator;

    public static final int MODE_DISABLED = 0;
    public static final int MODE_DRAWER = 1;
    public static final int MODE_UP = 2;

    private int mode = MODE_DISABLED;

    public DrawerToggle(Activity activity, DrawerLayout drawerLayout) {
        this.activity = activity;
        this.drawerLayout = drawerLayout;
        if (C.API_LOLLIPOP) {
            arrowDrawable = new ArrowDrawable(activity);
            slideDrawable = null;
        } else {
            arrowDrawable = null;
            homeAsUpIndicator = getThemeUpIndicatorObsolete();
            slideDrawable = new SlideDrawable(activity);
        }
    }

    private static final int DRAWER_CLOSE_DURATION;

    static {
        int duration;
        try {
            Field baseSettleDurationField = ViewDragHelper.class.getDeclaredField("BASE_SETTLE_DURATION");
            baseSettleDurationField.setAccessible(true);
            duration = (int) baseSettleDurationField.get(null);
        } catch (Exception e) {
            duration = 256;
        }
        DRAWER_CLOSE_DURATION = duration;
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    public void setDrawerIndicatorMode(int mode) {
        if (this.mode != mode) {
            this.mode = mode;
            ActionBar actionBar = activity.getActionBar();
            if (mode == MODE_DISABLED) {
                if (C.API_JELLY_BEAN_MR2) {
                    actionBar.setHomeAsUpIndicator(null);
                }
                actionBar.setDisplayHomeAsUpEnabled(false);
            } else {
                actionBar.setDisplayHomeAsUpEnabled(true);
                if (C.API_LOLLIPOP) {
                    activity.getActionBar().setHomeAsUpIndicator(arrowDrawable);
                    boolean open = drawerLayout.isDrawerOpen(Gravity.START) && arrowDrawable.position == 1f;
                    if (!open) {
                        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
                        animator.setDuration(DRAWER_CLOSE_DURATION);
                        animator.addUpdateListener(new StateArrowAnimatorListener(mode == MODE_DRAWER));
                        animator.start();
                    }
                } else {
                    setActionBarUpIndicatorObsolete(mode == MODE_DRAWER ? slideDrawable : homeAsUpIndicator);
                }
            }
        }
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    public void syncState() {
        if (mode != MODE_DISABLED) {
            if (C.API_LOLLIPOP) {
                arrowDrawable.setPosition(mode == MODE_UP || drawerLayout.isDrawerOpen(Gravity.START) ? 1f : 0f);
                activity.getActionBar().setHomeAsUpIndicator(arrowDrawable);
            } else {
                slideDrawable.setPosition(drawerLayout.isDrawerOpen(Gravity.START) ? 1f : 0f);
                setActionBarUpIndicatorObsolete(mode == MODE_DRAWER ? slideDrawable : homeAsUpIndicator);
            }
        }
    }

    public void onConfigurationChanged() {
        if (!C.API_LOLLIPOP) {
            homeAsUpIndicator = getThemeUpIndicatorObsolete();
        }
        syncState();
    }

    public boolean onOptionsItemSelected(MenuItem item) {
        if (item != null && item.getItemId() == android.R.id.home) {
            if (drawerLayout.getDrawerLockMode(Gravity.START) != DrawerLayout.LOCK_MODE_UNLOCKED) {
                return false;
            }
            if (mode == MODE_DRAWER) {
                if (drawerLayout.isDrawerVisible(Gravity.START)) {
                    drawerLayout.closeDrawer(Gravity.START);
                } else {
                    drawerLayout.openDrawer(Gravity.START);
                }
                return true;
            } else if (mode == MODE_UP) {
                if (drawerLayout.isDrawerVisible(Gravity.START)) {
                    drawerLayout.closeDrawer(Gravity.START);
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void onDrawerSlide(View drawerView, float slideOffset) {
        if (C.API_LOLLIPOP) {
            if (mode == MODE_DRAWER) {
                arrowDrawable.setPosition(slideOffset);
            }
        } else {
            float glyphOffset = slideDrawable.getPosition();
            if (slideOffset > 0.5f) {
                glyphOffset = Math.max(glyphOffset, Math.max(0.f, slideOffset - 0.5f) * 2);
            } else {
                glyphOffset = Math.min(glyphOffset, slideOffset * 2);
            }
            slideDrawable.setPosition(glyphOffset);
        }
    }

    @Override
    public void onDrawerOpened(View drawerView) {
        if (C.API_LOLLIPOP) {
            if (mode == MODE_DRAWER) {
                arrowDrawable.setPosition(1f);
            }
        } else {
            slideDrawable.setPosition(1);
        }
    }

    @Override
    public void onDrawerClosed(View drawerView) {
        if (C.API_LOLLIPOP) {
            if (mode == MODE_DRAWER) {
                arrowDrawable.setPosition(0f);
            }
        } else {
            slideDrawable.setPosition(0);
        }
    }

    @Override
    public void onDrawerStateChanged(int newState) {
    }

    private static final float ARROW_HEAD_ANGLE = (float) Math.toRadians(45);

    private class ArrowDrawable extends Drawable {
        private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        private final Path path = new Path();

        private final float barThickness;
        private final float topBottomArrowSize;
        private final float barSize;
        private final float middleArrowSize;
        private final float barGap;
        private final int size;

        private boolean verticalMirror = false;
        private float position;

        public ArrowDrawable(Context context) {
            paint.setAntiAlias(true);
            paint.setColor(0xffffffff);
            float density = ResourceUtils.obtainDensity(context);
            size = (int) (24f * density);
            barSize = 16f * density;
            topBottomArrowSize = 9.5f * density;
            barThickness = 2f * density;
            barGap = 3f * density;
            middleArrowSize = 13.6f * density;
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeJoin(Paint.Join.ROUND);
            paint.setStrokeCap(Paint.Cap.SQUARE);
            paint.setStrokeWidth(barThickness);
        }

        public void setPosition(float position) {
            position = Math.min(1f, Math.max(0f, position));
            if (position == 1f) {
                verticalMirror = true;
            } else if (position == 0f) {
                verticalMirror = false;
            }
            this.position = position;
            invalidateSelf();
        }

        @Override
        public int getIntrinsicWidth() {
            return size;
        }

        @Override
        public int getIntrinsicHeight() {
            return size;
        }

        @Override
        public void draw(Canvas canvas) {
            Rect bounds = getBounds();
            boolean rtl = isLayoutRtl();
            float position = this.position;
            float arrowSize = AnimationUtils.lerp(barSize, topBottomArrowSize, position);
            float middleBarSize = AnimationUtils.lerp(barSize, middleArrowSize, position);
            float middleBarCut = AnimationUtils.lerp(0f, barThickness / 2f, position);
            float rotation = AnimationUtils.lerp(0f, ARROW_HEAD_ANGLE, position);
            float canvasRotate = AnimationUtils.lerp(rtl ? 0f : -180f, rtl ? 180f : 0f, position);
            float topBottomBarOffset = AnimationUtils.lerp(barGap + barThickness, 0f, position);
            path.rewind();
            float arrowEdge = -middleBarSize / 2f + 0.5f;
            path.moveTo(arrowEdge + middleBarCut, 0f);
            path.rLineTo(middleBarSize - middleBarCut, 0f);
            float arrowWidth = Math.round(arrowSize * Math.cos(rotation));
            float arrowHeight = Math.round(arrowSize * Math.sin(rotation));
            path.moveTo(arrowEdge, topBottomBarOffset);
            path.rLineTo(arrowWidth, arrowHeight);
            path.moveTo(arrowEdge, -topBottomBarOffset);
            path.rLineTo(arrowWidth, -arrowHeight);
            path.moveTo(0f, 0f);
            path.close();
            canvas.save();
            canvas.rotate(canvasRotate * ((verticalMirror ^ rtl) ? -1f : 1f), bounds.centerX(), bounds.centerY());
            canvas.translate(bounds.centerX(), bounds.centerY());
            canvas.drawPath(path, paint);
            canvas.restore();
        }

        @Override
        public void setAlpha(int alpha) {
        }

        @Override
        public void setColorFilter(ColorFilter colorFilter) {
        }

        @Override
        public int getOpacity() {
            return PixelFormat.TRANSLUCENT;
        }
    }

    private class StateArrowAnimatorListener implements ValueAnimator.AnimatorUpdateListener {
        private final boolean enable;

        public StateArrowAnimatorListener(boolean enable) {
            this.enable = enable;
        }

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float value = (float) animation.getAnimatedValue();
            arrowDrawable.setPosition(enable ? 1f - value : value);
        }
    }

    private class SlideDrawable extends Drawable {
        private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        private final int size;

        private float position;

        private SlideDrawable(Context context) {
            paint.setColor(0xff979797);
            float density = ResourceUtils.obtainDensity(context);
            size = (int) (16f * density);
        }

        public void setPosition(float position) {
            this.position = position;
            invalidateSelf();
        }

        public float getPosition() {
            return position;
        }

        @Override
        public int getIntrinsicWidth() {
            return size;
        }

        @Override
        public int getIntrinsicHeight() {
            return size;
        }

        @Override
        public void draw(Canvas canvas) {
            Rect bounds = getBounds();
            canvas.save();
            canvas.translate(bounds.left, bounds.top);
            boolean rtl = isLayoutRtl();
            if (rtl) {
                canvas.translate(bounds.width(), 0);
                canvas.scale(-1, 1);
            }
            canvas.scale(bounds.width() / 48f, bounds.height() / 48f);
            canvas.translate(-16f * position, 0);
            canvas.drawRect(0, 4, 30, 12, paint);
            canvas.drawRect(0, 22, 30, 30, paint);
            canvas.drawRect(0, 40, 30, 48, paint);
            canvas.restore();
        }

        @Override
        public void setAlpha(int alpha) {
        }

        @Override
        public void setColorFilter(ColorFilter colorFilter) {
        }

        @Override
        public int getOpacity() {
            return PixelFormat.TRANSLUCENT;
        }
    }

    private static final int[] THEME_ATTRS = new int[] { android.R.attr.homeAsUpIndicator };

    private Drawable getThemeUpIndicatorObsolete() {
        if (C.API_JELLY_BEAN_MR2) {
            TypedArray a = activity.getActionBar().getThemedContext().obtainStyledAttributes(null, THEME_ATTRS,
                    android.R.attr.actionBarStyle, 0);
            Drawable result = a.getDrawable(0);
            a.recycle();
            return result;
        } else {
            TypedArray a = activity.obtainStyledAttributes(THEME_ATTRS);
            Drawable result = a.getDrawable(0);
            a.recycle();
            return result;
        }
    }

    private ImageView upIndicatorView;

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    private void setActionBarUpIndicatorObsolete(Drawable upDrawable) {
        if (C.API_JELLY_BEAN_MR2) {
            activity.getActionBar().setHomeAsUpIndicator(upDrawable);
        } else {
            if (upIndicatorView == null) {
                View home = activity.findViewById(android.R.id.home);
                if (home == null) {
                    return;
                }
                ViewGroup parent = (ViewGroup) home.getParent();
                int childCount = parent.getChildCount();
                if (childCount != 2) {
                    return;
                }
                View first = parent.getChildAt(0);
                View second = parent.getChildAt(1);
                View up = first.getId() == android.R.id.home ? second : first;
                if (up instanceof ImageView) {
                    upIndicatorView = (ImageView) up;
                }
            }
            if (upIndicatorView != null) {
                upIndicatorView.setImageDrawable(upDrawable);
            }
        }
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    private boolean isLayoutRtl() {
        return C.API_JELLY_BEAN_MR1
                ? activity.getWindow().getDecorView().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
                : false;
    }
}