Java tutorial
/* * Copyright 2015 Vikram Kakkar * * 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.appeaser.sublimenavigationviewlibrary; import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Typeface; import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.LayoutRes; import android.support.annotation.MenuRes; import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.View; /** * Top level view that hosts a SublimeMenu */ public class SublimeNavigationView extends ScrimInsetsFrameLayout { private static final String TAG = SublimeNavigationView.class.getSimpleName(); // Key used for saving state private static final String SS_MENU = "ss.menu"; // Menu private SublimeMenu mMenu; // Presenter private final SublimeMenuPresenter mPresenter; // Listener private OnNavigationMenuEventListener mEventListener; // Max width set for this drawer private int mMaxWidth; // Custom MenuInflater private SublimeMenuInflater mMenuInflater; // Theme controller private SublimeThemer mThemer; public SublimeNavigationView(Context context) { this(context, null); } public SublimeNavigationView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SublimeNavigationView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SublimeNavigationView, defStyleAttr, R.style.SnvSublimeNavigationView); try { // Used for creating default resources SublimeThemer.DefaultTheme defaultTheme = SublimeThemer.DefaultTheme.LIGHT; if (a.hasValue(R.styleable.SublimeNavigationView_snvDefaultTheme)) { defaultTheme = a.getInt(R.styleable.SublimeNavigationView_snvDefaultTheme, 0) == 0 ? SublimeThemer.DefaultTheme.LIGHT : SublimeThemer.DefaultTheme.DARK; } mThemer = new SublimeThemer(getContext(), defaultTheme); mThemer.setDrawerBackground(a.getDrawable(R.styleable.SublimeNavigationView_android_background)); if (a.hasValue(R.styleable.SublimeNavigationView_elevation)) { mThemer.setElevation( (float) a.getDimensionPixelSize(R.styleable.SublimeNavigationView_elevation, 0)); } ViewCompat.setFitsSystemWindows(this, a.getBoolean(R.styleable.SublimeNavigationView_android_fitsSystemWindows, false)); mMaxWidth = a.getDimensionPixelSize(R.styleable.SublimeNavigationView_android_maxWidth, 0); if (a.hasValue(R.styleable.SublimeNavigationView_snvItemIconTint)) { mThemer.setIconTintList(a.getColorStateList(R.styleable.SublimeNavigationView_snvItemIconTint)); } mThemer.setGroupExpandDrawable(a.getDrawable(R.styleable.SublimeNavigationView_snvGroupExpandDrawable)); mThemer.setGroupCollapseDrawable( a.getDrawable(R.styleable.SublimeNavigationView_snvGroupCollapseDrawable)); // Text style profiles for Item, Hint, SubheaderItem & SubheaderHint ColorStateList itemTextColor = null, hintTextColor = null, subheaderItemTextColor = null, subheaderHintTextColor = null, badgeTextColor = null; Typeface itemTypeface = null, hintTypeface = null, subheaderItemTypeface = null, subheaderHintTypeface = null, badgeTypeface = null; int itemTypefaceStyle = 0, hintTypefaceStyle = 0, subheaderItemTypefaceStyle = 0, subheaderHintTypefaceStyle = 0, badgeTypefaceStyle = 0; if (a.hasValue(R.styleable.SublimeNavigationView_snvItemTextColor)) { itemTextColor = a.getColorStateList(R.styleable.SublimeNavigationView_snvItemTextColor); } if (a.hasValue(R.styleable.SublimeNavigationView_snvHintTextColor)) { hintTextColor = a.getColorStateList(R.styleable.SublimeNavigationView_snvHintTextColor); } if (a.hasValue(R.styleable.SublimeNavigationView_snvSubheaderItemTextColor)) { subheaderItemTextColor = a .getColorStateList(R.styleable.SublimeNavigationView_snvSubheaderItemTextColor); } if (a.hasValue(R.styleable.SublimeNavigationView_snvSubheaderHintTextColor)) { subheaderHintTextColor = a .getColorStateList(R.styleable.SublimeNavigationView_snvSubheaderHintTextColor); } if (a.hasValue(R.styleable.SublimeNavigationView_snvBadgeTextColor)) { badgeTextColor = a.getColorStateList(R.styleable.SublimeNavigationView_snvBadgeTextColor); } try { // Catch the RuntimeException thrown if // the Typeface filename is incorrect if (a.hasValue(R.styleable.SublimeNavigationView_snvItemTypefaceFilename)) { String itemTypefaceFilename = a .getString(R.styleable.SublimeNavigationView_snvItemTypefaceFilename); if (!TextUtils.isEmpty(itemTypefaceFilename)) { itemTypeface = Typeface.createFromAsset(context.getAssets(), itemTypefaceFilename); } } if (a.hasValue(R.styleable.SublimeNavigationView_snvHintTypefaceFilename)) { String hintTypefaceFilename = a .getString(R.styleable.SublimeNavigationView_snvHintTypefaceFilename); if (!TextUtils.isEmpty(hintTypefaceFilename)) { hintTypeface = Typeface.createFromAsset(context.getAssets(), hintTypefaceFilename); } } if (a.hasValue(R.styleable.SublimeNavigationView_snvSubheaderItemTypefaceFilename)) { String subheaderItemTypefaceFilename = a .getString(R.styleable.SublimeNavigationView_snvSubheaderItemTypefaceFilename); if (!TextUtils.isEmpty(subheaderItemTypefaceFilename)) { subheaderItemTypeface = Typeface.createFromAsset(context.getAssets(), subheaderItemTypefaceFilename); } } if (a.hasValue(R.styleable.SublimeNavigationView_snvSubheaderHintTypefaceFilename)) { String subheaderHintTypefaceFilename = a .getString(R.styleable.SublimeNavigationView_snvSubheaderHintTypefaceFilename); if (!TextUtils.isEmpty(subheaderHintTypefaceFilename)) { subheaderHintTypeface = Typeface.createFromAsset(context.getAssets(), subheaderHintTypefaceFilename); } } if (a.hasValue(R.styleable.SublimeNavigationView_snvBadgeTypefaceFilename)) { String badgeTypefaceFilename = a .getString(R.styleable.SublimeNavigationView_snvBadgeTypefaceFilename); if (!TextUtils.isEmpty(badgeTypefaceFilename)) { badgeTypeface = Typeface.createFromAsset(context.getAssets(), badgeTypefaceFilename); } } } catch (RuntimeException re) { Log.e(TAG, "Error loading Typeface from Assets. " + "Confirm that the Typeface filename is correct:\n" + " - filename should include the extension\n" + " - filename is case-sensitive"); } if (a.hasValue(R.styleable.SublimeNavigationView_snvItemTypefaceStyle)) { itemTypefaceStyle = a.getInt(R.styleable.SublimeNavigationView_snvItemTypefaceStyle, Typeface.NORMAL); switch (itemTypefaceStyle) { case 1: itemTypefaceStyle = Typeface.BOLD; break; case 2: itemTypefaceStyle = Typeface.ITALIC; break; case 3: itemTypefaceStyle = Typeface.BOLD_ITALIC; break; default: // case 0: NORMAL itemTypefaceStyle = Typeface.NORMAL; break; } } if (a.hasValue(R.styleable.SublimeNavigationView_snvHintTypefaceStyle)) { hintTypefaceStyle = a.getInt(R.styleable.SublimeNavigationView_snvHintTypefaceStyle, Typeface.NORMAL); switch (hintTypefaceStyle) { case 1: hintTypefaceStyle = Typeface.BOLD; break; case 2: hintTypefaceStyle = Typeface.ITALIC; break; case 3: hintTypefaceStyle = Typeface.BOLD_ITALIC; break; default: // case 0: NORMAL hintTypefaceStyle = Typeface.NORMAL; break; } } if (a.hasValue(R.styleable.SublimeNavigationView_snvSubheaderItemTypefaceStyle)) { subheaderItemTypefaceStyle = a .getInt(R.styleable.SublimeNavigationView_snvSubheaderItemTypefaceStyle, Typeface.NORMAL); switch (subheaderItemTypefaceStyle) { case 1: subheaderItemTypefaceStyle = Typeface.BOLD; break; case 2: subheaderItemTypefaceStyle = Typeface.ITALIC; break; case 3: subheaderItemTypefaceStyle = Typeface.BOLD_ITALIC; break; default: // case 0: NORMAL subheaderItemTypefaceStyle = Typeface.NORMAL; break; } } if (a.hasValue(R.styleable.SublimeNavigationView_snvSubheaderHintTypefaceStyle)) { subheaderHintTypefaceStyle = a .getInt(R.styleable.SublimeNavigationView_snvSubheaderHintTypefaceStyle, Typeface.NORMAL); switch (subheaderHintTypefaceStyle) { case 1: subheaderHintTypefaceStyle = Typeface.BOLD; break; case 2: subheaderHintTypefaceStyle = Typeface.ITALIC; break; case 3: subheaderHintTypefaceStyle = Typeface.BOLD_ITALIC; break; default: // case 0: NORMAL subheaderHintTypefaceStyle = Typeface.NORMAL; break; } } if (a.hasValue(R.styleable.SublimeNavigationView_snvBadgeTypefaceStyle)) { badgeTypefaceStyle = a.getInt(R.styleable.SublimeNavigationView_snvBadgeTypefaceStyle, Typeface.NORMAL); switch (badgeTypefaceStyle) { case 1: badgeTypefaceStyle = Typeface.BOLD; break; case 2: badgeTypefaceStyle = Typeface.ITALIC; break; case 3: badgeTypefaceStyle = Typeface.BOLD_ITALIC; break; default: // case 0: NORMAL badgeTypefaceStyle = Typeface.NORMAL; break; } } // Item text styling TextViewStyleProfile itemStyleProfile = new TextViewStyleProfile(context, defaultTheme); itemStyleProfile.setTextColor(itemTextColor).setTypeface(itemTypeface) .setTypefaceStyle(itemTypefaceStyle); mThemer.setItemStyleProfile(itemStyleProfile); // Hint text styling TextViewStyleProfile hintStyleProfile = new TextViewStyleProfile(context, defaultTheme); hintStyleProfile.setTextColor(hintTextColor).setTypeface(hintTypeface) .setTypefaceStyle(hintTypefaceStyle); mThemer.setItemHintStyleProfile(hintStyleProfile); // Sub-header item text styling TextViewStyleProfile subheaderItemStyleProfile = new TextViewStyleProfile(context, defaultTheme); subheaderItemStyleProfile.setTextColor(subheaderItemTextColor).setTypeface(subheaderItemTypeface) .setTypefaceStyle(subheaderItemTypefaceStyle); mThemer.setSubheaderStyleProfile(subheaderItemStyleProfile); // Sub-header hint text styling TextViewStyleProfile subheaderHintStyleProfile = new TextViewStyleProfile(context, defaultTheme); subheaderHintStyleProfile.setTextColor(subheaderHintTextColor).setTypeface(subheaderHintTypeface) .setTypefaceStyle(subheaderHintTypefaceStyle); mThemer.setSubheaderHintStyleProfile(subheaderHintStyleProfile); // Badge text styling TextViewStyleProfile badgeStyleProfile = new TextViewStyleProfile(context, defaultTheme); badgeStyleProfile.setTextColor(badgeTextColor).setTypeface(badgeTypeface) .setTypefaceStyle(badgeTypefaceStyle); mThemer.setBadgeStyleProfile(badgeStyleProfile); mThemer.setItemBackground(a.getDrawable(R.styleable.SublimeNavigationView_snvItemBackground)); if (a.hasValue(R.styleable.SublimeNavigationView_snvMenu)) { int menuResId = a.getResourceId(R.styleable.SublimeNavigationView_snvMenu, -1); if (menuResId == -1) { throw new RuntimeException("Passed menuResId was not valid"); } mMenu = new SublimeMenu(menuResId); inflateMenu(menuResId); } mMenu.setCallback(new SublimeMenu.Callback() { public boolean onMenuItemSelected(SublimeMenu menu, SublimeBaseMenuItem item, OnNavigationMenuEventListener.Event event) { return SublimeNavigationView.this.mEventListener != null && SublimeNavigationView.this.mEventListener.onNavigationMenuEvent(event, item); } }); mPresenter = new SublimeMenuPresenter(); applyThemer(); mMenu.setMenuPresenter(getContext(), mPresenter); addView(mPresenter.getMenuView(this)); if (a.hasValue(R.styleable.SublimeNavigationView_snvHeaderLayout)) { inflateHeaderView(a.getResourceId(R.styleable.SublimeNavigationView_snvHeaderLayout, 0)); } } finally { a.recycle(); } // Upon creation, and until initializations are done, // SublimeMenuPresenter blocks all calls for invalidation. // We can now finalize the initialization phase to allow // invalidation of the menu when required. mPresenter.setInitializationDone(); } /** * Provides a mechanism for switching between any number of Menus. * * @param newMenuResId id of the menu that you wish * to switch to. Eg: R.menu.new_menu_id */ public void switchMenuTo(@MenuRes int newMenuResId) { if (newMenuResId < 1) { Log.e(TAG, "Could not switch to new menu: passed menuResourceId was invalid."); return; } mMenu = new SublimeMenu(newMenuResId); inflateMenu(newMenuResId); mMenu.setCallback(new SublimeMenu.Callback() { public boolean onMenuItemSelected(SublimeMenu menu, SublimeBaseMenuItem item, OnNavigationMenuEventListener.Event event) { return SublimeNavigationView.this.mEventListener != null && SublimeNavigationView.this.mEventListener.onNavigationMenuEvent(event, item); } }); mMenu.setMenuPresenter(getContext(), mPresenter); } /** * Provides a mechanism for switching between any number of Menus. * * @param newMenu Typically, this would * have been returned from a former call to * {@link SublimeNavigationView#getMenu()}. */ public void switchMenuTo(@NonNull SublimeMenu newMenu) { // Todo: pending removal of this NULL check if (newMenu == null) { Log.e(TAG, "Could not switch to new menu: passed menu was 'null'."); return; } mMenu = newMenu; mMenu.setCallback(new SublimeMenu.Callback() { public boolean onMenuItemSelected(SublimeMenu menu, SublimeBaseMenuItem item, OnNavigationMenuEventListener.Event event) { return SublimeNavigationView.this.mEventListener != null && SublimeNavigationView.this.mEventListener.onNavigationMenuEvent(event, item); } }); mMenu.setMenuPresenter(getContext(), mPresenter); } /** * Returns the currently set header view. * * @return Currently set header {@link View}. Null, if the header * is not set. */ public View getHeaderView() { return mPresenter.getHeaderView(); } @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SublimeNavigationView.SavedState state = new SublimeNavigationView.SavedState(superState); state.getMenuState().putParcelable(SS_MENU, mMenu); return state; } @Override protected void onRestoreInstanceState(Parcelable savedState) { SublimeNavigationView.SavedState state = (SublimeNavigationView.SavedState) savedState; super.onRestoreInstanceState(state.getSuperState()); Bundle menuState = state.getMenuState(); if (menuState != null && menuState.containsKey(SS_MENU)) { mMenu = menuState.getParcelable(SS_MENU); } if (mMenu != null) { mMenu.setCallback(new SublimeMenu.Callback() { public boolean onMenuItemSelected(SublimeMenu menu, SublimeBaseMenuItem item, OnNavigationMenuEventListener.Event event) { return SublimeNavigationView.this.mEventListener != null && SublimeNavigationView.this.mEventListener.onNavigationMenuEvent(event, item); } }); mMenu.setMenuPresenter(getContext(), mPresenter); } } /** * Sets the listener. * * @param listener Listener */ public void setNavigationMenuEventListener(OnNavigationMenuEventListener listener) { mEventListener = listener; } @Override protected void onMeasure(int widthSpec, int heightSpec) { switch (View.MeasureSpec.getMode(widthSpec)) { case View.MeasureSpec.AT_MOST: widthSpec = View.MeasureSpec.makeMeasureSpec(Math.min(View.MeasureSpec.getSize(widthSpec), mMaxWidth), View.MeasureSpec.EXACTLY); break; case View.MeasureSpec.UNSPECIFIED: widthSpec = View.MeasureSpec.makeMeasureSpec(mMaxWidth, View.MeasureSpec.EXACTLY); case View.MeasureSpec.EXACTLY: } super.onMeasure(widthSpec, heightSpec); } /** * Inflates menu resource with the given ID into the * current {@link SublimeMenu} item. * * @param menuResId ID of the resource to inflate. */ private void inflateMenu(@MenuRes int menuResId) { getMenuInflater().inflate(menuResId, mMenu); } /** * Returns the current {@link SublimeMenu} item. * * @return current {@link SublimeMenu}. */ @NonNull public SublimeMenu getMenu() { return mMenu; } /** * Inflates and returns the view resource with the given ID. * * @param res view resource ID to inflate. * @return inflated {@link View}. */ private View inflateHeaderView(@LayoutRes int res) { return mPresenter.inflateHeaderView(res); } /** * Sets the given {@link View} as the header this Menu. * * @param view the {@link View} to add as header. */ public void addHeaderView(@NonNull View view) { mPresenter.addHeaderView(view); } /** * Removes the given {@link View} from the header. * * @param view the {@link View} to remove from header. */ public void removeHeaderView(@NonNull View view) { mPresenter.removeHeaderView(view); } /** * Used internally to create a custom inflater for xml * definition of a {@link SublimeMenu}. * * @return inflater that can work with xml definition * of a {@link SublimeMenu}. */ @NonNull private SublimeMenuInflater getMenuInflater() { if (mMenuInflater == null) { mMenuInflater = new SublimeMenuInflater(getContext()); } return mMenuInflater; } /** * Returns the currently used {@link SublimeThemer}. * Note that several method that provide access to * theme elements has been removed from {@link SublimeNavigationView}. * For example, to gain access to the currently used * Icon Tint List (accessible through 'NavigationView#getItemIconTintList()'), * call {@link SublimeThemer#getIconTintList()}. * * @return Currently used {@link SublimeThemer}. */ @NonNull public SublimeThemer getCurrentThemer() { return mThemer; } /** * Style navigation view components through java (instead of xml). * Note that {@link SublimeThemer} is currently not * preserved when saving state. * * @param sublimeThemer A valid {@link SublimeThemer}. Method does nothing * if this parameter is NULL. */ public void updateThemer(@NonNull SublimeThemer sublimeThemer) { // Todo: pending removal of this NULL check if (sublimeThemer == null) { Log.e(TAG, "'updateThemer(SublimeThemer)' was called with a 'null' value"); return; } mThemer = sublimeThemer; applyThemer(); } /** * Used internally to apply the currently set {@link SublimeThemer}. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void applyThemer() { Log.i(TAG, "applyThemer()"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { setBackground(mThemer.getDrawerBackground()); } else { setBackgroundDrawable(mThemer.getDrawerBackground()); } ViewCompat.setElevation(this, mThemer.getElevation()); // propagate themer to the presenter mPresenter.setThemer(mThemer); } //----------------------------------------------------------------// //------------------------Preserving State------------------------// //----------------------------------------------------------------// public static class SavedState extends View.BaseSavedState { public Bundle sMenuState; public static final Creator<SublimeNavigationView.SavedState> CREATOR = new Creator<SublimeNavigationView.SavedState>() { public SublimeNavigationView.SavedState createFromParcel(Parcel parcel) { return new SublimeNavigationView.SavedState(parcel); } public SublimeNavigationView.SavedState[] newArray(int size) { return new SublimeNavigationView.SavedState[size]; } }; public SavedState(Parcel in) { super(in); sMenuState = in.readBundle(); } public SavedState(Parcelable superState) { super(superState); sMenuState = new Bundle(); } public Bundle getMenuState() { return sMenuState; } public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeBundle(sMenuState); } } }