Repository: wjwang0914/wj-todo-wanandroid Branch: master Commit: 91046a88307e Files: 67 Total size: 198.9 KB Directory structure: gitextract_pavxei7w/ ├── .gitignore ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── wj/ │ │ └── android/ │ │ └── todo/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── wj/ │ │ │ └── android/ │ │ │ └── todo/ │ │ │ ├── MainApplication.java │ │ │ ├── activity/ │ │ │ │ ├── AddTodoActivity.java │ │ │ │ ├── EditTodoActivity.java │ │ │ │ ├── LoginActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── RegisterActivity.java │ │ │ │ ├── SplashActivity.java │ │ │ │ └── base/ │ │ │ │ └── BaseActivity.java │ │ │ ├── adapter/ │ │ │ │ └── TodoSectionAdapter.java │ │ │ ├── bean/ │ │ │ │ ├── TodoDesBean.java │ │ │ │ ├── TodoListBean.java │ │ │ │ └── TodoSection.java │ │ │ ├── constant/ │ │ │ │ ├── Constant.java │ │ │ │ └── TimeConstants.java │ │ │ ├── exception/ │ │ │ │ └── ApiException.java │ │ │ ├── fragment/ │ │ │ │ ├── SettingFragment.java │ │ │ │ ├── TodoFragment.java │ │ │ │ └── base/ │ │ │ │ └── BaseFragment.java │ │ │ ├── http/ │ │ │ │ ├── HttpUtils.java │ │ │ │ ├── MyGsonCallback.java │ │ │ │ └── ResponseItem.java │ │ │ ├── manager/ │ │ │ │ ├── PersistentCookieJarManager.java │ │ │ │ ├── PreferenceHelper.java │ │ │ │ └── SharePreferenceManager.java │ │ │ ├── util/ │ │ │ │ └── TimeUtils.java │ │ │ └── widget/ │ │ │ └── BottomNavigationViewEx.java │ │ └── res/ │ │ ├── color/ │ │ │ ├── selector_color.xml │ │ │ └── selector_nav_item_color.xml │ │ ├── drawable/ │ │ │ ├── ic_complete.xml │ │ │ ├── ic_setting.xml │ │ │ ├── ic_todo.xml │ │ │ └── selector_button.xml │ │ ├── layout/ │ │ │ ├── activity_add_todo.xml │ │ │ ├── activity_edit_todo.xml │ │ │ ├── activity_login.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_register.xml │ │ │ ├── activity_splash.xml │ │ │ ├── dialog_todo_des_view.xml │ │ │ ├── fragment_complete.xml │ │ │ ├── fragment_setting.xml │ │ │ ├── fragment_todo.xml │ │ │ ├── title_bar_layout.xml │ │ │ ├── todo_item_head.xml │ │ │ └── todo_item_view.xml │ │ ├── menu/ │ │ │ └── navigation.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── xml/ │ │ └── network_security_config.xml │ └── test/ │ └── java/ │ └── com/ │ └── wj/ │ └── android/ │ └── todo/ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── release/ │ └── app-release.apk └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle .idea /local.properties /.idea/libraries /.idea/modules.xml /.idea/workspace.xml .DS_Store /build /captures .externalNativeBuild ================================================ FILE: README.md ================================================ # wj-todo-wanandroid 用心打造一款极致体验的TODO开源客户端,数据接口来自鸿神的玩Android,不放过每一个细节,用心写代码,如果您觉得还不错的话,就点个Star吧~(您的每次支持,是我开源的动力) # APK app-release.apk # 项目架构 该项目使用最简单的MVC架构,整体代码实现层次分明,高内聚低耦合,代码逻辑清晰,通俗易懂,使用BottomNavigationView+ViewPager+Fragment完成UI主体实现,引入butterknife依赖注入框架,简化了代码的编写,网络层的编写,主要是引入了我另一个开源框架wj-http(主要是对Retrofit2进行了二次封装,方便使用,提升开发效率)
# 项目运行截图
* author: Blankj * blog : http://blankj.com * time : 2017/03/13 * desc : constants of time **/ public final class TimeConstants { public static final int MSEC = 1; public static final int SEC = 1000; public static final int MIN = 60000; public static final int HOUR = 3600000; public static final int DAY = 86400000; @IntDef({MSEC, SEC, MIN, HOUR, DAY}) @Retention(RetentionPolicy.SOURCE) public @interface Unit { } } ================================================ FILE: app/src/main/java/com/wj/android/todo/exception/ApiException.java ================================================ package com.wj.android.todo.exception; /** * 作者:wangwnejie on 2018/8/8 14:06 * 邮箱:wang20080990@163.com */ public class ApiException extends RuntimeException{ private int errorCode; public ApiException(String message, int errorCode) { super(message); setErrorCode(errorCode); } public int getErrorCode() { return errorCode; } public void setErrorCode(int errorCode) { this.errorCode = errorCode; } } ================================================ FILE: app/src/main/java/com/wj/android/todo/fragment/SettingFragment.java ================================================ package com.wj.android.todo.fragment; import android.os.Bundle; import android.text.TextUtils; import android.widget.TextView; import com.wj.android.todo.R; import com.wj.android.todo.activity.LoginActivity; import com.wj.android.todo.activity.MainActivity; import com.wj.android.todo.fragment.base.BaseFragment; import com.wj.android.todo.manager.PersistentCookieJarManager; import com.wj.android.todo.manager.SharePreferenceManager; import java.util.List; import butterknife.BindView; import butterknife.OnClick; import okhttp3.Cookie; /** * 作者:wangwnejie on 2018/8/8 13:42 * 邮箱:wang20080990@163.com */ public class SettingFragment extends BaseFragment { @BindView(R.id.user_name) TextView mUserName; public static SettingFragment newInstance() { Bundle args = new Bundle(); SettingFragment fragment = new SettingFragment(); fragment.setArguments(args); return fragment; } @Override protected int getLayoutId() { return R.layout.fragment_setting; } @Override protected void initData() { String userName = SharePreferenceManager.getInstance(getContext()).getUserName(); if (!TextUtils.isEmpty(userName)) { mUserName.setText(SharePreferenceManager.getInstance(getContext()).getUserName()); } } @OnClick(R.id.logout) void onClickLogout() { SharePreferenceManager.getInstance(getContext()).clear(); PersistentCookieJarManager.getInstance(getContext()).getPersistentCookieJar().clear(); startActivity(LoginActivity.class); getActivity().finish(); } } ================================================ FILE: app/src/main/java/com/wj/android/todo/fragment/TodoFragment.java ================================================ package com.wj.android.todo.fragment; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.BottomSheetDialog; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.View; import android.widget.TextView; import com.chad.library.adapter.base.BaseQuickAdapter; import com.wj.android.todo.R; import com.wj.android.todo.activity.EditTodoActivity; import com.wj.android.todo.activity.MainActivity; import com.wj.android.todo.adapter.TodoSectionAdapter; import com.wj.android.todo.bean.TodoDesBean; import com.wj.android.todo.bean.TodoListBean; import com.wj.android.todo.bean.TodoSection; import com.wj.android.todo.fragment.base.BaseFragment; import com.wj.android.todo.http.HttpUtils; import com.wj.android.todo.http.ResponseItem; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import butterknife.BindView; /** * 作者:wangwnejie on 2018/8/8 13:42 * 邮箱:wang20080990@163.com */ public class TodoFragment extends BaseFragment { private static final String KEY_IS_DONE = "is_done"; private static final int REQUEST_CODE_EDIT_TODO = 0x110; @BindView(R.id.todo_rv) RecyclerView mRecyclerView; @BindView(R.id.swipe_layout) SwipeRefreshLayout mSwipeRefreshLayout; private int page = 1; private TodoListBean mTodoListBean; private TodoSectionAdapter mAdapter; private int deletePosition = -1; private int donePosition = -1; private boolean isDone; public static TodoFragment newInstance(boolean isDone) { Bundle args = new Bundle(); args.putBoolean(KEY_IS_DONE, isDone); TodoFragment fragment = new TodoFragment(); fragment.setArguments(args); return fragment; } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle bundle = getArguments(); if (bundle != null) { isDone = bundle.getBoolean(KEY_IS_DONE); } } @Override protected int getLayoutId() { return R.layout.fragment_todo; } @Override protected void initData() { mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { page = 1; requestTodoListData(); } }); requestTodoListData(); } private void requestTodoListData() { HttpUtils.requestTodoList(this, page, isDone); } private void deleteTodoById(int todoId) { HttpUtils.deleteTodoById(this,todoId); } private void doneTodoById(int todoId) { HttpUtils.doneTodoById(this, todoId, isDone ? 0 : 1); } public void updateUI(final ResponseItem
* author: Blankj * blog : http://blankj.com * time : 2016/08/02 * desc : utils about time **/ public final class TimeUtils { private static final ThreadLocal
The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param millis The milliseconds. * @return the formatted time string */ public static String millis2String(final long millis) { return millis2String(millis, getDefaultFormat()); } /** * Milliseconds to the formatted time string. * * @param millis The milliseconds. * @param format The format. * @return the formatted time string */ public static String millis2String(final long millis, @NonNull final DateFormat format) { return format.format(new Date(millis)); } /** * Formatted time string to the milliseconds. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time The formatted time string. * @return the milliseconds */ public static long string2Millis(final String time) { return string2Millis(time, getDefaultFormat()); } /** * Formatted time string to the milliseconds. * * @param time The formatted time string. * @param format The format. * @return the milliseconds */ public static long string2Millis(final String time, @NonNull final DateFormat format) { try { return format.parse(time).getTime(); } catch (ParseException e) { e.printStackTrace(); } return -1; } /** * Formatted time string to the date. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time The formatted time string. * @return the date */ public static Date string2Date(final String time) { return string2Date(time, getDefaultFormat()); } /** * Formatted time string to the date. * * @param time The formatted time string. * @param format The format. * @return the date */ public static Date string2Date(final String time, @NonNull final DateFormat format) { try { return format.parse(time); } catch (ParseException e) { e.printStackTrace(); } return null; } /** * Date to the formatted time string. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param date The date. * @return the formatted time string */ public static String date2String(final Date date) { return date2String(date, getDefaultFormat()); } /** * Date to the formatted time string. * * @param date The date. * @param format The format. * @return the formatted time string */ public static String date2String(final Date date, @NonNull final DateFormat format) { return format.format(date); } public static String date2String(final Date date, @NonNull final String pattern) { return date2String(date, getDateFormat(pattern)); } /** * Date to the milliseconds. * * @param date The date. * @return the milliseconds */ public static long date2Millis(final Date date) { return date.getTime(); } /** * Milliseconds to the date. * * @param millis The milliseconds. * @return the date */ public static Date millis2Date(final long millis) { return new Date(millis); } /** * Return the time span, in unit. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time1 The first formatted time string. * @param time2 The second formatted time string. * @param unit The unit of time span. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time1 The first formatted time string. * @param time2 The second formatted time string. * @param precision The precision of time span. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @return the current formatted time string */ public static String getNowString() { return millis2String(System.currentTimeMillis(), getDefaultFormat()); } /** * Return the current formatted time string. * * @param format The format. * @return the current formatted time string */ public static String getNowString(@NonNull final DateFormat format) { return millis2String(System.currentTimeMillis(), format); } /** * Return the current date. * * @return the current date */ public static Date getNowDate() { return new Date(); } /** * Return the time span by now, in unit. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time The formatted time string. * @param unit The unit of time span. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time The formatted time string. * @param precision The precision of time span. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time The formatted time string. * @return the friendly time span by now *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time The formatted time string. * @param timeSpan The time span. * @param unit The unit of time span. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param millis The milliseconds. * @param timeSpan The time span. * @param unit The unit of time span. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time The formatted time string. * @param timeSpan The time span. * @param unit The unit of time span. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param date The date. * @param timeSpan The time span. * @param unit The unit of time span. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time The formatted time string. * @param timeSpan The time span. * @param unit The unit of time span. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param timeSpan The time span. * @param unit The unit of time span. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time The formatted time string. * @return {@code true}: yesThe pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time The formatted time string. * @return {@code true}: yesThe pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time The formatted time string. * @return the day of week in Chinese */ public static String getChineseWeek(final String time) { return getChineseWeek(string2Date(time, getDefaultFormat())); } /** * Return the day of week in Chinese. * * @param time The formatted time string. * @param format The format. * @return the day of week in Chinese */ public static String getChineseWeek(final String time, @NonNull final DateFormat format) { return getChineseWeek(string2Date(time, format)); } /** * Return the day of week in Chinese. * * @param date The date. * @return the day of week in Chinese */ public static String getChineseWeek(final Date date) { return new SimpleDateFormat("E", Locale.CHINA).format(date); } /** * Return the day of week in Chinese. * * @param millis The milliseconds. * @return the day of week in Chinese */ public static String getChineseWeek(final long millis) { return getChineseWeek(new Date(millis)); } /** * Return the day of week in US. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time The formatted time string. * @return the day of week in US */ public static String getUSWeek(final String time) { return getUSWeek(string2Date(time, getDefaultFormat())); } /** * Return the day of week in US. * * @param time The formatted time string. * @param format The format. * @return the day of week in US */ public static String getUSWeek(final String time, @NonNull final DateFormat format) { return getUSWeek(string2Date(time, format)); } /** * Return the day of week in US. * * @param date The date. * @return the day of week in US */ public static String getUSWeek(final Date date) { return new SimpleDateFormat("EEEE", Locale.US).format(date); } /** * Return the day of week in US. * * @param millis The milliseconds. * @return the day of week in US */ public static String getUSWeek(final long millis) { return getUSWeek(new Date(millis)); } /** * Returns the value of the given calendar field. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time The formatted time string. * @param field The given calendar field. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time The formatted time string. * @return the Chinese zodiac */ public static String getChineseZodiac(final String time) { return getChineseZodiac(string2Date(time, getDefaultFormat())); } /** * Return the Chinese zodiac. * * @param time The formatted time string. * @param format The format. * @return the Chinese zodiac */ public static String getChineseZodiac(final String time, @NonNull final DateFormat format) { return getChineseZodiac(string2Date(time, format)); } /** * Return the Chinese zodiac. * * @param date The date. * @return the Chinese zodiac */ public static String getChineseZodiac(final Date date) { Calendar cal = Calendar.getInstance(); cal.setTime(date); return CHINESE_ZODIAC[cal.get(Calendar.YEAR) % 12]; } /** * Return the Chinese zodiac. * * @param millis The milliseconds. * @return the Chinese zodiac */ public static String getChineseZodiac(final long millis) { return getChineseZodiac(millis2Date(millis)); } /** * Return the Chinese zodiac. * * @param year The year. * @return the Chinese zodiac */ public static String getChineseZodiac(final int year) { return CHINESE_ZODIAC[year % 12]; } private static final int[] ZODIAC_FLAGS = {20, 19, 21, 21, 21, 22, 23, 23, 23, 24, 23, 22}; private static final String[] ZODIAC = { "水瓶座", "双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座", "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "魔羯座" }; /** * Return the zodiac. *The pattern is {@code yyyy-MM-dd HH:mm:ss}.
* * @param time The formatted time string. * @return the zodiac */ public static String getZodiac(final String time) { return getZodiac(string2Date(time, getDefaultFormat())); } /** * Return the zodiac. * * @param time The formatted time string. * @param format The format. * @return the zodiac */ public static String getZodiac(final String time, @NonNull final DateFormat format) { return getZodiac(string2Date(time, format)); } /** * Return the zodiac. * * @param date The date. * @return the zodiac */ public static String getZodiac(final Date date) { Calendar cal = Calendar.getInstance(); cal.setTime(date); int month = cal.get(Calendar.MONTH) + 1; int day = cal.get(Calendar.DAY_OF_MONTH); return getZodiac(month, day); } /** * Return the zodiac. * * @param millis The milliseconds. * @return the zodiac */ public static String getZodiac(final long millis) { return getZodiac(millis2Date(millis)); } /** * Return the zodiac. * * @param month The month. * @param day The day. * @return the zodiac */ public static String getZodiac(final int month, final int day) { return ZODIAC[day >= ZODIAC_FLAGS[month - 1] ? month - 1 : (month + 10) % 12]; } private static long timeSpan2Millis(final long timeSpan, @TimeConstants.Unit final int unit) { return timeSpan * unit; } private static long millis2TimeSpan(final long millis, @TimeConstants.Unit final int unit) { return millis / unit; } private static String millis2FitTimeSpan(long millis, int precision) { if (precision <= 0) return null; precision = Math.min(precision, 5); String[] units = {"天", "小时", "分钟", "秒", "毫秒"}; if (millis == 0) return 0 + units[precision - 1]; StringBuilder sb = new StringBuilder(); if (millis < 0) { sb.append("-"); millis = -millis; } int[] unitLen = {86400000, 3600000, 60000, 1000, 1}; for (int i = 0; i < precision; i++) { if (millis >= unitLen[i]) { long mode = millis / unitLen[i]; millis -= mode * unitLen[i]; sb.append(mode).append(units[i]); } } return sb.toString(); } } ================================================ FILE: app/src/main/java/com/wj/android/todo/widget/BottomNavigationViewEx.java ================================================ package com.wj.android.todo.widget; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Paint; import android.graphics.Typeface; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.internal.BottomNavigationItemView; import android.support.design.internal.BottomNavigationMenuView; import android.support.design.widget.BottomNavigationView; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.util.SparseIntArray; import android.util.TypedValue; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import java.lang.ref.WeakReference; import java.lang.reflect.Field; /** * Created by yu on 2016/11/10. */ public class BottomNavigationViewEx extends BottomNavigationView { // used for animation private int mShiftAmount; private float mScaleUpFactor; private float mScaleDownFactor; private boolean animationRecord; private float mLargeLabelSize; private float mSmallLabelSize; private boolean visibilityTextSizeRecord; private boolean visibilityHeightRecord; private int mItemHeight; private boolean textVisibility = true; // used for animation end // used for setupWithViewPager private ViewPager mViewPager; private MyOnNavigationItemSelectedListener mMyOnNavigationItemSelectedListener; private BottomNavigationViewExOnPageChangeListener mPageChangeListener; private BottomNavigationMenuView mMenuView; private BottomNavigationItemView[] mButtons; // used for setupWithViewPager end // detect navigation tab changes when the user clicking on navigation item private static boolean isNavigationItemClicking = false; public BottomNavigationViewEx(Context context) { super(context); } public BottomNavigationViewEx(Context context, AttributeSet attrs) { super(context, attrs); } public BottomNavigationViewEx(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @SuppressLint("RestrictedApi") private void refreshTextViewVisibility() { if (!textVisibility) return; // 1. get mMenuView BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); // 2. get mButtons BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); int currentItem = getCurrentItem(); // 3. get field mShiftingMode and TextView in mButtons for (BottomNavigationItemView button : mButtons) { TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel"); TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel"); mLargeLabel.clearAnimation(); mSmallLabel.clearAnimation(); // mShiftingMode boolean mShiftingMode = getField(button.getClass(), button, "mShiftingMode"); boolean selected = button.getItemPosition() == currentItem; if (mShiftingMode) { if (selected) { mLargeLabel.setVisibility(VISIBLE); } else { mLargeLabel.setVisibility(INVISIBLE); } mSmallLabel.setVisibility(INVISIBLE); } else { if (selected) { mLargeLabel.setVisibility(VISIBLE); mSmallLabel.setVisibility(INVISIBLE); } else { mLargeLabel.setVisibility(INVISIBLE); mSmallLabel.setVisibility(VISIBLE); } } } } /** * change the visibility of icon * * @param visibility */ @SuppressLint("RestrictedApi") public void setIconVisibility(boolean visibility) { /* 1. get field in this class private final BottomNavigationMenuView mMenuView; 2. get field in mButtons private BottomNavigationItemView[] mButtons; 3. get mIcon in mButtons private ImageView mIcon 4. set mIcon visibility gone 5. change mItemHeight to only text size in mMenuView */ // 1. get mMenuView final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); // 2. get mButtons BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); // 3. get mIcon in mButtons for (BottomNavigationItemView button : mButtons) { ImageView mIcon = getField(button.getClass(), button, "mIcon"); // 4. set mIcon visibility gone mIcon.setVisibility(visibility ? View.VISIBLE : View.INVISIBLE); } // 5. change mItemHeight to only text size in mMenuView if (!visibility) { // if not record mItemHeight if (!visibilityHeightRecord) { visibilityHeightRecord = true; mItemHeight = getItemHeight(); } // change mItemHeight BottomNavigationItemView button = mButtons[0]; if (null != button) { final ImageView mIcon = getField(button.getClass(), button, "mIcon"); // System.out.println("mIcon.getMeasuredHeight():" + mIcon.getMeasuredHeight()); if (null != mIcon) { mIcon.post(new Runnable() { @Override public void run() { // System.out.println("mIcon.getMeasuredHeight():" + mIcon.getMeasuredHeight()); setItemHeight(mItemHeight - mIcon.getMeasuredHeight()); } }); } } } else { // if not record the mItemHeight, we need do nothing. if (!visibilityHeightRecord) return; // restore it setItemHeight(mItemHeight); } mMenuView.updateMenuView(); } /** * change the visibility of text * * @param visibility */ @SuppressLint("RestrictedApi") public void setTextVisibility(boolean visibility) { this.textVisibility = visibility; /* 1. get field in this class private final BottomNavigationMenuView mMenuView; 2. get field in mButtons private BottomNavigationItemView[] mButtons; 3. set text size in mButtons private final TextView mLargeLabel private final TextView mSmallLabel 4. change mItemHeight to only icon size in mMenuView */ // 1. get mMenuView BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); // 2. get mButtons BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); // 3. change field mShiftingMode value in mButtons for (BottomNavigationItemView button : mButtons) { TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel"); TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel"); if (!visibility) { // if not record the font size, record it if (!visibilityTextSizeRecord && !animationRecord) { visibilityTextSizeRecord = true; mLargeLabelSize = mLargeLabel.getTextSize(); mSmallLabelSize = mSmallLabel.getTextSize(); } // if not visitable, set font size to 0 mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, 0); mSmallLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, 0); } else { // if not record the font size, we need do nothing. if (!visibilityTextSizeRecord) break; // restore it mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLargeLabelSize); mSmallLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSmallLabelSize); } } // 4 change mItemHeight to only icon size in mMenuView if (!visibility) { // if not record mItemHeight if (!visibilityHeightRecord) { visibilityHeightRecord = true; mItemHeight = getItemHeight(); } // change mItemHeight to only icon size in mMenuView // private final int mItemHeight; // change mItemHeight // System.out.println("mLargeLabel.getMeasuredHeight():" + getFontHeight(mSmallLabelSize)); setItemHeight(mItemHeight - getFontHeight(mSmallLabelSize)); } else { // if not record the mItemHeight, we need do nothing. if (!visibilityHeightRecord) return; // restore mItemHeight setItemHeight(mItemHeight); } mMenuView.updateMenuView(); } /** * get text height by font size * * @param fontSize * @return */ private static int getFontHeight(float fontSize) { Paint paint = new Paint(); paint.setTextSize(fontSize); Paint.FontMetrics fm = paint.getFontMetrics(); return (int) Math.ceil(fm.descent - fm.top) + 2; } /** * enable or disable click item animation(text scale and icon move animation in no item shifting mode) * * @param enable It means the text won't scale and icon won't move when active it in no item shifting mode if false. */ @SuppressLint("RestrictedApi") public void enableAnimation(boolean enable) { /* 1. get field in this class private final BottomNavigationMenuView mMenuView; 2. get field in mButtons private BottomNavigationItemView[] mButtons; 3. chang mShiftAmount to 0 in mButtons private final int mShiftAmount change mScaleUpFactor and mScaleDownFactor to 1f in mButtons private final float mScaleUpFactor private final float mScaleDownFactor 4. change label font size in mButtons private final TextView mLargeLabel private final TextView mSmallLabel */ // 1. get mMenuView BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); // 2. get mButtons BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); // 3. change field mShiftingMode value in mButtons for (BottomNavigationItemView button : mButtons) { TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel"); TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel"); // if disable animation, need animationRecord the source value if (!enable) { if (!animationRecord) { animationRecord = true; mShiftAmount = getField(button.getClass(), button, "mShiftAmount"); mScaleUpFactor = getField(button.getClass(), button, "mScaleUpFactor"); mScaleDownFactor = getField(button.getClass(), button, "mScaleDownFactor"); mLargeLabelSize = mLargeLabel.getTextSize(); mSmallLabelSize = mSmallLabel.getTextSize(); // System.out.println("mShiftAmount:" + mShiftAmount + " mScaleUpFactor:" // + mScaleUpFactor + " mScaleDownFactor:" + mScaleDownFactor // + " mLargeLabel:" + mLargeLabelSize + " mSmallLabel:" + mSmallLabelSize); } // disable setField(button.getClass(), button, "mShiftAmount", 0); setField(button.getClass(), button, "mScaleUpFactor", 1); setField(button.getClass(), button, "mScaleDownFactor", 1); // let the mLargeLabel font size equal to mSmallLabel mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSmallLabelSize); // debug start // mLargeLabelSize = mLargeLabel.getTextSize(); // System.out.println("mLargeLabel:" + mLargeLabelSize); // debug end } else { // haven't change the value. It means it was the first call this method. So nothing need to do. if (!animationRecord) return; // enable animation setField(button.getClass(), button, "mShiftAmount", mShiftAmount); setField(button.getClass(), button, "mScaleUpFactor", mScaleUpFactor); setField(button.getClass(), button, "mScaleDownFactor", mScaleDownFactor); // restore mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLargeLabelSize); } } mMenuView.updateMenuView(); } /** * enable the shifting mode for navigation * * @param enable It will has a shift animation if true. Otherwise all items are the same width. */ @SuppressLint("RestrictedApi") public void enableShiftingMode(boolean enable) { /* 1. get field in this class private final BottomNavigationMenuView mMenuView; 2. change field mShiftingMode value in mMenuView private boolean mShiftingMode = true; */ // 1. get mMenuView BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); // 2. change field mShiftingMode value in mMenuView setField(mMenuView.getClass(), mMenuView, "mShiftingMode", enable); mMenuView.updateMenuView(); } /** * enable the shifting mode for each item * * @param enable It will has a shift animation for item if true. Otherwise the item text always be shown. */ @SuppressLint("RestrictedApi") public void enableItemShiftingMode(boolean enable) { /* 1. get field in this class private final BottomNavigationMenuView mMenuView; 2. get field in this mMenuView private BottomNavigationItemView[] mButtons; 3. change field mShiftingMode value in mButtons private boolean mShiftingMode = true; */ // 1. get mMenuView BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); // 2. get mButtons BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); // 3. change field mShiftingMode value in mButtons for (BottomNavigationItemView button : mButtons) { setField(button.getClass(), button, "mShiftingMode", enable); } mMenuView.updateMenuView(); } /** * get the current checked item position * * @return index of item, start from 0. */ public int getCurrentItem() { /* 1. get field in this class private final BottomNavigationMenuView mMenuView; 2. get field in mMenuView private BottomNavigationItemView[] mButtons; 3. get menu and traverse it to get the checked one */ // 1. get mMenuView // BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); // 2. get mButtons BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); // 3. get menu and traverse it to get the checked one Menu menu = getMenu(); for (int i = 0; i < mButtons.length; i++) { if (menu.getItem(i).isChecked()) { return i; } } return 0; } /** * get menu item position in menu * * @param item * @return position if success, -1 otherwise */ public int getMenuItemPosition(MenuItem item) { // get item id int itemId = item.getItemId(); // get meunu Menu menu = getMenu(); int size = menu.size(); for (int i = 0; i < size; i++) { if (menu.getItem(i).getItemId() == itemId) { return i; } } return -1; } /** * set the current checked item * * @param item start from 0. */ public void setCurrentItem(int item) { // check bounds if (item < 0 || item >= getMaxItemCount()) { throw new ArrayIndexOutOfBoundsException("item is out of bounds, we expected 0 - " + (getMaxItemCount() - 1) + ". Actually " + item); } /* 1. get field in this class private final BottomNavigationMenuView mMenuView; 2. get field in mMenuView private BottomNavigationItemView[] mButtons; private final OnClickListener mOnClickListener; 3. call mOnClickListener.onClick(); */ // 1. get mMenuView BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); // 2. get mButtons BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); // get mOnClickListener OnClickListener mOnClickListener = getField(mMenuView.getClass(), mMenuView, "onClickListener"); // System.out.println("mMenuView:" + mMenuView + " mButtons:" + mButtons + " mOnClickListener" + mOnClickListener); // 3. call mOnClickListener.onClick(); mOnClickListener.onClick(mButtons[item]); } /** * get OnNavigationItemSelectedListener * * @return */ public OnNavigationItemSelectedListener getOnNavigationItemSelectedListener() { // private OnNavigationItemSelectedListener mListener; OnNavigationItemSelectedListener mListener = getField(BottomNavigationView.class, this, "selectedListener"); return mListener; } @Override public void setOnNavigationItemSelectedListener(@Nullable OnNavigationItemSelectedListener listener) { // if not set up with view pager, the same with father if (null == mMyOnNavigationItemSelectedListener) { super.setOnNavigationItemSelectedListener(listener); return; } mMyOnNavigationItemSelectedListener.setOnNavigationItemSelectedListener(listener); } /** * get private mMenuView * * @return */ private BottomNavigationMenuView getBottomNavigationMenuView() { if (null == mMenuView) mMenuView = getField(BottomNavigationView.class, this, "menuView"); return mMenuView; } /** * get private mButtons in mMenuView * * @return */ public BottomNavigationItemView[] getBottomNavigationItemViews() { if (null != mButtons) return mButtons; /* * 1 private final BottomNavigationMenuView mMenuView; * 2 private BottomNavigationItemView[] mButtons; */ BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); mButtons = getField(mMenuView.getClass(), mMenuView, "buttons"); return mButtons; } /** * get private mButton in mMenuView at position * * @param position * @return */ public BottomNavigationItemView getBottomNavigationItemView(int position) { return getBottomNavigationItemViews()[position]; } /** * get icon at position * * @param position * @return */ public ImageView getIconAt(int position) { /* * 1 private final BottomNavigationMenuView mMenuView; * 2 private BottomNavigationItemView[] mButtons; * 3 private ImageView mIcon; */ BottomNavigationItemView mButtons = getBottomNavigationItemView(position); ImageView mIcon = getField(BottomNavigationItemView.class, mButtons, "icon"); return mIcon; } /** * get small label at position * Each item has tow label, one is large, another is small. * * @param position * @return */ public TextView getSmallLabelAt(int position) { /* * 1 private final BottomNavigationMenuView mMenuView; * 2 private BottomNavigationItemView[] mButtons; * 3 private final TextView mSmallLabel; */ BottomNavigationItemView mButtons = getBottomNavigationItemView(position); TextView mSmallLabel = getField(BottomNavigationItemView.class, mButtons, "smallLabel"); return mSmallLabel; } /** * get large label at position * Each item has tow label, one is large, another is small. * * @param position * @return */ public TextView getLargeLabelAt(int position) { /* * 1 private final BottomNavigationMenuView mMenuView; * 2 private BottomNavigationItemView[] mButtons; * 3 private final TextView mLargeLabel; */ BottomNavigationItemView mButtons = getBottomNavigationItemView(position); TextView mLargeLabel = getField(BottomNavigationItemView.class, mButtons, "largeLabel"); return mLargeLabel; } /** * return item count * * @return */ public int getItemCount() { BottomNavigationItemView[] bottomNavigationItemViews = getBottomNavigationItemViews(); if (null == bottomNavigationItemViews) return 0; return bottomNavigationItemViews.length; } /** * set all item small TextView size * Each item has tow label, one is large, another is small. * Small one will be shown when item state is normal * Large one will be shown when item checked. * * @param sp */ @SuppressLint("RestrictedApi") public void setSmallTextSize(float sp) { int count = getItemCount(); for (int i = 0; i < count; i++) { getSmallLabelAt(i).setTextSize(sp); } mMenuView.updateMenuView(); } /** * set all item large TextView size * Each item has tow label, one is large, another is small. * Small one will be shown when item state is normal. * Large one will be shown when item checked. * * @param sp */ @SuppressLint("RestrictedApi") public void setLargeTextSize(float sp) { int count = getItemCount(); for (int i = 0; i < count; i++) { getLargeLabelAt(i).setTextSize(sp); } mMenuView.updateMenuView(); } /** * set all item large and small TextView size * Each item has tow label, one is large, another is small. * Small one will be shown when item state is normal * Large one will be shown when item checked. * * @param sp */ public void setTextSize(float sp) { setLargeTextSize(sp); setSmallTextSize(sp); } /** * set item ImageView size which at position * * @param position position start from 0 * @param width in dp * @param height in dp */ @SuppressLint("RestrictedApi") public void setIconSizeAt(int position, float width, float height) { ImageView icon = getIconAt(position); // update size ViewGroup.LayoutParams layoutParams = icon.getLayoutParams(); layoutParams.width = dp2px(getContext(), width); layoutParams.height = dp2px(getContext(), height); icon.setLayoutParams(layoutParams); mMenuView.updateMenuView(); } /** * set all item ImageView size * * @param width in dp * @param height in dp */ public void setIconSize(float width, float height) { int count = getItemCount(); for (int i = 0; i < count; i++) { setIconSizeAt(i, width, height); } } /** * set menu item height * * @param height in px */ @SuppressLint("RestrictedApi") public void setItemHeight(int height) { // 1. get mMenuView final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); // 2. set private final int mItemHeight in mMenuView setField(mMenuView.getClass(), mMenuView, "itemHeight", height); mMenuView.updateMenuView(); } /** * get menu item height * * @return in px */ public int getItemHeight() { // 1. get mMenuView final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); // 2. get private final int mItemHeight in mMenuView return getField(mMenuView.getClass(), mMenuView, "itemHeight"); } /** * dp to px * * @param context * @param dpValue dp * @return px */ public static int dp2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } /** * set Typeface for all item TextView * * @attr ref android.R.styleable#TextView_typeface * @attr ref android.R.styleable#TextView_textStyle */ @SuppressLint("RestrictedApi") public void setTypeface(Typeface typeface, int style) { int count = getItemCount(); for (int i = 0; i < count; i++) { getLargeLabelAt(i).setTypeface(typeface, style); getSmallLabelAt(i).setTypeface(typeface, style); } mMenuView.updateMenuView(); } /** * set Typeface for all item TextView * * @attr ref android.R.styleable#TextView_typeface */ @SuppressLint("RestrictedApi") public void setTypeface(Typeface typeface) { int count = getItemCount(); for (int i = 0; i < count; i++) { getLargeLabelAt(i).setTypeface(typeface); getSmallLabelAt(i).setTypeface(typeface); } mMenuView.updateMenuView(); } /** * get private filed in this specific object * * @param targetClass * @param instance the filed owner * @param fieldName * @param*
This class stores the provided BottomNavigationViewEx weakly, meaning that you can use
* {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener)
* addOnPageChangeListener(OnPageChangeListener)} without removing the listener and
* not cause a leak.
*/
private static class BottomNavigationViewExOnPageChangeListener implements ViewPager.OnPageChangeListener {
private final WeakReference