com.nikola.despotoski.drawerlayoutedgetoggle.DrawerLayoutEdgeToggle.java Source code

Java tutorial

Introduction

Here is the source code for com.nikola.despotoski.drawerlayoutedgetoggle.DrawerLayoutEdgeToggle.java

Source

package com.nikola.despotoski.drawerlayoutedgetoggle;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.ViewDragHelper;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.util.TypedValue;
import android.view.Display;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
import android.widget.ImageView;

import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.animation.ValueAnimator;
import com.nineoldandroids.animation.ValueAnimator.AnimatorUpdateListener;
import com.nineoldandroids.view.ViewHelper;

/* Author: Nikola Despotoski
 * Email: nikola[dot]despotoski(at)gmail[dot]com
 * 
 *   Copyright (c) 2012 Nikola Despotoski
    
   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.
 */
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public class DrawerLayoutEdgeToggle implements DrawerLayout.DrawerListener {

    public static final int HANDLE_CENTER = 50; //percents of the y-axis
    public static final int HANDLE_TOP = 0;
    public static final int HANDLE_BOTTOM = 100;
    private Activity mActivity;
    private DrawerLayout mDrawerLayout;
    private ImageView mHandle;
    private float mMinusShadow;
    private Drawable mOpenDrawable;
    private Drawable mCloseDrawable;
    private boolean mKeepShadowOnHandle;
    private int MAX_ALPHA = 255;
    private float MAX_VIEW_ALPHA = 1.2f;
    private OnClickListener mHandleClickListener;
    private OnClickListener mHandleClickListenerInt = new OnClickListener() {

        @Override
        public void onClick(View v) {
            if (mDefaultOk) {
                if (!mDrawerLayout.isDrawerOpen(mGravity))
                    mDrawerLayout.openDrawer(mGravity);
                else
                    mDrawerLayout.closeDrawer(mGravity);
            }
            if (mHandleClickListener != null)
                mHandleClickListener.onClick(v);
        }

    };
    private OnTouchListener mHandleTouchListenerInt = new OnTouchListener() {

        private float mInitialX = 0.0f;

        @Override
        public boolean onTouch(View v, MotionEvent event) {

            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                mInitialX = event.getX();
            } else if (event.getAction() == MotionEvent.ACTION_UP) {
                mInitialX = 0.0f;
            }
            MotionEvent copy = MotionEvent.obtain(event);
            copy.setEdgeFlags(ViewDragHelper.EDGE_ALL);
            copy.setLocation(
                    mDrawerLayout.isDrawerOpen(GravityCompat.START) ? event.getX() : event.getX() - mInitialX,
                    event.getY());
            //   Log.i("copy.getRawX()", ""+copy.getRawX());
            //   Log.i("View x ", " "+ViewHelper.getX(mHandle));
            mDrawerLayout.onTouchEvent(copy);
            return mHandleTouchListener != null ? mHandleTouchListener.onTouch(v, event) : false;

        }
    };

    private boolean mDefaultOk = true;
    private int mCloseTitleRes = 0x0;
    private int mOpenTitleRes = 0x0;
    private OnTouchListener mHandleTouchListener;
    private int mGravity;
    private FrameLayout rootLayout;
    private Drawable mCurrentDrawable;
    private int mScreenWidth;
    private float mTopPercentage;
    private int mPreviousX = 0;
    private int mY = 0;

    public DrawerLayoutEdgeToggle(Activity a, DrawerLayout l, int drawerOpen, int drawerClose,
            boolean keepShadowOnHandle, int drawerGravity) {

        if (drawerGravity != GravityCompat.END && drawerGravity != GravityCompat.START
                && drawerGravity != Gravity.LEFT && drawerGravity != Gravity.RIGHT)
            throw new IllegalArgumentException(
                    "Use: GravityCompat.END, GravityCompat.START, Gravity.LEFT or Gravity.RIGHT for drawerGravity parameter");
        mGravity = drawerGravity;
        mActivity = a;
        mDrawerLayout = l;
        mOpenDrawable = a.getResources().getDrawable(drawerOpen);
        mCloseDrawable = a.getResources().getDrawable(drawerClose);
        rootLayout = (FrameLayout) mActivity.findViewById(android.R.id.content);
        mHandle = new ImageView(a);
        final ViewTreeObserver viewTreeObserver = mHandle.getViewTreeObserver();
        if (viewTreeObserver.isAlive()) {
            viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

                @SuppressLint("NewApi")
                @Override
                public void onGlobalLayout() {
                    mDrawerLayout.measure(MeasureSpec.EXACTLY, MeasureSpec.EXACTLY);
                    syncState();
                    removeOnGlobalLayoutListener(mHandle, this);
                }
            });
        }

        mHandle.setOnClickListener(mHandleClickListenerInt);
        mHandle.setOnTouchListener(mHandleTouchListenerInt);
        mHandle.setSaveEnabled(true);
        mDrawerLayout.setOnTouchListener(mHandleTouchListenerInt);
        rootLayout.addView(mHandle,
                new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, drawerGravity));
        mKeepShadowOnHandle = keepShadowOnHandle;
        mCurrentDrawable = mCloseDrawable;
    }

    public void setOverrideDefaultHandleAction(boolean ok) {
        mDefaultOk = ok;
    }

    public View getDrawerLayoutHandle() {
        return mHandle;
    }

    public void setOnHandleClickListener(View.OnClickListener listener) {
        mHandleClickListener = listener;
    }

    public void setOnHandleTouchListener(View.OnTouchListener listener) {
        mHandleTouchListener = listener;
    }

    @Override
    public void onDrawerClosed(View arg0) {
        mHandle.setImageDrawable(mCloseDrawable);
        updateActionBar();
        mCurrentDrawable = mCloseDrawable;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            ViewHelper.setX(mHandle,
                    mGravity == GravityCompat.END || mGravity == Gravity.RIGHT ? getScreenWidth() : 0);
            ViewHelper.setY(mHandle, mY);
        }

    }

    @SuppressWarnings("deprecation")
    @SuppressLint("NewApi")
    private void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener) {
        if (Build.VERSION.SDK_INT < 16) {
            v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
        } else {
            v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
        }
    }

    @Override
    public void onDrawerOpened(View arg0) {
        mHandle.setImageDrawable(mOpenDrawable);
        updateActionBar();
        mCurrentDrawable = mOpenDrawable;
    }

    public void setActionBarCloseTitle(int res) {
        mCloseTitleRes = res;
    }

    public void setActionBarOpenTitle(int res) {
        mOpenTitleRes = res;
    }

    @Override
    public void onDrawerSlide(View arg0, float slideOffset) {
        getDrawerMinusShadow();
        final float translationX = checkForLeftDrawer(mGravity, slideOffset * mMinusShadow);
        //  Log.i("translationX ", " "+translationX);
        if (Build.VERSION.SDK_INT >= 11) {
            mHandle.setTranslationX(translationX);
            mHandle.setX(translationX);
        } else {
            ObjectAnimator translator = ObjectAnimator.ofFloat(mHandle, "x", mPreviousX, translationX)
                    .setDuration(0);
            translator.addUpdateListener(mUpdateListener);
            translator.start();
            mPreviousX = (int) translationX;
        }
        updatShadowOnHandle(slideOffset);

    }

    private AnimatorUpdateListener mUpdateListener = new AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator arg0) {
            Float animatedValue = (Float) arg0.getAnimatedValue();
            ViewHelper.setX(mHandle, animatedValue.floatValue());
        }

    };

    @Override
    public void onDrawerStateChanged(int arg0) {

    }

    private float checkForLeftDrawer(int gravity, float x) {
        return gravity == GravityCompat.END || gravity == Gravity.LEFT
                ? mScreenWidth - x - mHandle.getDrawable().getIntrinsicWidth()
                : x;
    }

    private void getDrawerMinusShadow() {
        if (mMinusShadow == 0.0f) {
            for (int i = 0; i < mDrawerLayout.getChildCount(); i++) {
                mMinusShadow = mMinusShadow == 0.0f ? mDrawerLayout.getChildAt(i).getMeasuredWidth() : mMinusShadow;
                mMinusShadow = Math.min(mMinusShadow, mDrawerLayout.getChildAt(i).getMeasuredWidth());
            }
        }
        return;
    }

    private void keepShadow(float offset) {
        if (Build.VERSION.SDK_INT >= 11)
            mHandle.setAlpha(MAX_VIEW_ALPHA - offset);
        else
            mHandle.getDrawable().setAlpha((int) (MAX_ALPHA * (1.0f - offset)));
    }

    public void syncState() {
        mCurrentDrawable = mDrawerLayout.isDrawerOpen(mGravity) ? mOpenDrawable : mCloseDrawable;
        mScreenWidth = getScreenWidth();
        mHandle.setImageDrawable(mCurrentDrawable);
        updateActionBar();
        updateHandle();
        updateHandleVerticalPosition();
    }

    private void updatShadowOnHandle(float offset) {
        if (!mKeepShadowOnHandle)
            noShadow();
        else
            keepShadow(offset);
    }

    private void updateHandleVerticalPosition() {
        if (mTopPercentage > 100 || mTopPercentage < 0) {
            throw new IllegalArgumentException("Invalid percentage.");
        }
        //int initY = (int) (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? mHandle.getY() : ViewHelper.getY(mHandle));
        //if(initY > 0 || ) return; //set y once in the life cycle and avoid any future calls
        float decimal = mTopPercentage / 100;
        int screenHeight = mActivity.findViewById(android.R.id.content).getHeight();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            int y = (int) (screenHeight - (screenHeight * decimal));
            y = screenHeight - y;
            mHandle.setY(screenHeight - y);
        } else {
            /*
             * Height of the actionbar is actually 0 0 coordinate of the content view on devices < HC
             * Coordinate system;
             * __________> x
             * |
             * |
             * |
             * \/
             * y
             */
            screenHeight -= getActionBarHeight();
            int y = (int) (screenHeight - (screenHeight * decimal));
            int toAdd = getActionBarHeight() + (mHandle.getDrawable().getIntrinsicHeight() / 2);
            y = screenHeight - y + toAdd;
            FrameLayout.LayoutParams params = (LayoutParams) mHandle.getLayoutParams();
            params.topMargin = y - (mHandle.getDrawable().getIntrinsicHeight() / 2);
            mHandle.setLayoutParams(params);
            ViewHelper.setY(mHandle, y);
            mY = y;
        }
    }

    private void noShadow() {
        mCloseDrawable.setAlpha(MAX_ALPHA);
        mOpenDrawable.setAlpha(MAX_ALPHA);
    }

    private void updateHandle() {
        getDrawerMinusShadow();
        mScreenWidth = getScreenWidth();
        mHandle.setImageDrawable(mCurrentDrawable);
        if (isOpen()) {
            int x = (int) checkForLeftDrawer(mGravity, mMinusShadow);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
                mHandle.setX(x);
            else
                ViewHelper.setX(mHandle, x);
            updatShadowOnHandle(1.0f);

        }

    }

    @SuppressLint("NewApi")
    @SuppressWarnings("deprecation")
    private int getScreenWidth() {

        Point size = new Point();
        WindowManager w = mActivity.getWindowManager();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
            w.getDefaultDisplay().getSize(size);
            return size.x;
        } else {
            Display d = w.getDefaultDisplay();
            return d.getWidth();
        }
    }

    @SuppressLint("NewApi")
    private void updateActionBar() {
        if (mOpenTitleRes != 0x0 && mCloseTitleRes != 0x0)
            if (mOpenTitleRes != 0x0 && mCloseTitleRes != 0x0) {
                String title = isOpen() ? mActivity.getResources().getString(mOpenTitleRes)
                        : mActivity.getResources().getString(mCloseTitleRes);
                if (Build.VERSION.SDK_INT >= 11)
                    mActivity.getActionBar().setTitle(title);
                else if (mActivity instanceof ActionBarActivity) {
                    ((ActionBarActivity) mActivity).getSupportActionBar().setTitle(title);
                }
                //TODO ActionBarSherlock via Java Reflection, since appcompat and sherlock are mutually exclusive
            }
    }

    private boolean isOpen() {
        return mDrawerLayout.isDrawerOpen(mGravity);
    }

    public boolean onOptionsItemSelected(MenuItem item) {
        if (isOpen()) {
            mDrawerLayout.closeDrawer(mGravity);
        }
        return false;
    }

    public void setVerticalTopOffset(int topInPercentage) {
        mTopPercentage = topInPercentage;
    }

    public void onConfigurationChanged(Configuration newConfig) {
        if (mDrawerLayout != null) {
            mDrawerLayout.measure(MeasureSpec.EXACTLY, MeasureSpec.EXACTLY);
            syncState();
        }
    }

    @SuppressLint("NewApi")
    @SuppressWarnings("deprecation")
    private int getScreenHeight() {
        Point size = new Point();
        WindowManager w = mActivity.getWindowManager();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            w.getDefaultDisplay().getSize(size);
            return size.y;
        } else {
            Display d = w.getDefaultDisplay();
            return d.getHeight();
        }
    }

    private int iterateAttributesForActionBarSherlock() {
        PackageInfo packageInfo;
        try {
            packageInfo = mActivity.getPackageManager().getPackageInfo(mActivity.getPackageName(),
                    PackageManager.GET_META_DATA);
            int themeId = packageInfo.applicationInfo.theme;
            TypedArray themeAttributes = mActivity.getResources().obtainTypedArray(themeId);
            for (int i = 0; i < themeAttributes.length(); i++) {
                try {
                    TypedValue v = new TypedValue();
                    themeAttributes.getValue(i, v);
                    String attName = mActivity.getResources().getResourceEntryName(v.resourceId);
                    if (attName != null && attName.contains("actionbarsherlock")
                            && attName.contains("actionBarSize")) {
                        return v.data;
                    }
                } catch (Exception e) {
                    Log.e("ResourceNotFound", "" + e.getMessage());
                }
            }
            themeAttributes.recycle();
        } catch (NameNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return 0;

    }

    //
    // Credits to Andreas Bangerl (andreas[dot]bangerl[at]mkw[dot]at) for few suggested fixes.
    private int getActionBarHeight() {
        TypedValue tv = new TypedValue();
        int actionBarHeight = 0;
        if (mActivity.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
            actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,
                    mActivity.getResources().getDisplayMetrics());
        else if (actionBarHeight == 0 && mActivity instanceof ActionBarActivity) {
            actionBarHeight = (int) mActivity.getResources().getDimension(R.dimen.abc_action_bar_default_height);
        } else {
            actionBarHeight = iterateAttributesForActionBarSherlock();
        }
        return actionBarHeight;
    }

}