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进行了二次封装,方便使用,提升开发效率) # 项目运行截图
## Thanks ### API: 鸿洋大大提供的 [WanAndroid API](http://www.wanandroid.com/) ### ICON: [iconfont](http://www.iconfont.cn/) ================================================ FILE: app/.gitignore ================================================ /build /release /keystore *.iml ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 28 defaultConfig { applicationId "com.wj.android.todo" minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } signingConfigs { release { keyAlias 'wj' keyPassword '19900914' storeFile file('keystore/wj.jks') storePassword '19900914' } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' buildConfigField('String', 'BASE_URL', FORMAL_BASE_URL) } debug { buildConfigField('String', 'BASE_URL', TEST_BASE_URL) } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:28.0.0-beta01' implementation 'com.android.support:design:28.0.0-beta01' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation project(":wj-http") implementation 'com.jakewharton:butterknife:8.8.1' annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.40' implementation 'com.android.support:recyclerview-v7:28.0.0-beta01' implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1' //implementation 'com.afollestad.material-dialogs:core:0.9.6.0' } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: app/src/androidTest/java/com/wj/android/todo/ExampleInstrumentedTest.java ================================================ package com.wj.android.todo; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumented test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.wj.android.todo", appContext.getPackageName()); } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/wj/android/todo/MainApplication.java ================================================ package com.wj.android.todo; import android.app.Application; import com.wj.android.http.XRetrofit; import com.wj.android.todo.manager.PersistentCookieJarManager; /** * 作者:wangwnejie on 2018/8/7 16:03 * 邮箱:wang20080990@163.com */ public class MainApplication extends Application { @Override public void onCreate() { super.onCreate(); XRetrofit.init() .debug(BuildConfig.DEBUG) .cookieJar(PersistentCookieJarManager.getInstance(this).getPersistentCookieJar()); } } ================================================ FILE: app/src/main/java/com/wj/android/todo/activity/AddTodoActivity.java ================================================ package com.wj.android.todo.activity; import android.app.DatePickerDialog; import android.content.Intent; import android.support.design.widget.TextInputEditText; import android.text.TextUtils; import android.view.View; import android.widget.DatePicker; import android.widget.ImageView; import android.widget.TextView; import com.google.gson.JsonObject; import com.wj.android.todo.R; import com.wj.android.todo.activity.base.BaseActivity; import com.wj.android.todo.bean.TodoDesBean; import com.wj.android.todo.http.HttpUtils; import com.wj.android.todo.http.ResponseItem; import com.wj.android.todo.util.TimeUtils; import java.util.Calendar; import java.util.Date; import butterknife.BindView; import butterknife.OnClick; /** * 作者:wangwnejie on 2018/8/8 19:21 * 邮箱:wang20080990@163.com */ public class AddTodoActivity extends BaseActivity { @BindView(R.id.title) TextView mTitle; @BindView(R.id.back) ImageView mBack; @BindView(R.id.todo_date) TextView mTodoDate; @BindView(R.id.todo_name) TextInputEditText mTodoName; @BindView(R.id.todo_des) TextInputEditText mTodoDes; @Override protected int getLayoutId() { return R.layout.activity_add_todo; } @Override protected void initData() { mTitle.setText(R.string.add_todo); mBack.setVisibility(View.VISIBLE); mTodoDate.setText(TimeUtils.date2String(new Date(),"yyyy-MM-dd")); } @OnClick(R.id.back) void onClickBack() { finish(); } @OnClick(R.id.todo_date) void onClickTodoDate() { Calendar calendar = Calendar.getInstance(); DatePickerDialog datePickerDialog = new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() { @Override public void onDateSet(DatePicker datePicker, int year, int month, int dayOfMonth) { mTodoDate.setText(String.format("%d-%d-%d", year, month+1, dayOfMonth)); } },calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)); datePickerDialog.getDatePicker().setMinDate(new Date().getTime()); datePickerDialog.show(); } @OnClick(R.id.save_todo) void onClickAddTodo() { mTodoName.setError(null); if (TextUtils.isEmpty(mTodoName.getText())) { mTodoName.setError(getString(R.string.input_todo_name_toast)); mTodoName.setFocusable(true); mTodoName.setFocusableInTouchMode(true); mTodoName.requestFocus(); return; } requestAddTodoData(); } private void requestAddTodoData() { HttpUtils.requestAddTodoData(this, mTodoName.getText().toString(), mTodoDes.getText().toString(), mTodoDate.getText().toString()); } public void updateUI(ResponseItem response) { if (response.isSuccess()) { showToast(getString(R.string.add_todo_success)); Intent intent = new Intent(); intent.putExtra("add_todo", response.getData()); setResult(0x200, intent); finish(); } } @Override public boolean isLoadingEnable(int requestId) { return super.isLoadingEnable(requestId); } } ================================================ FILE: app/src/main/java/com/wj/android/todo/activity/EditTodoActivity.java ================================================ package com.wj.android.todo.activity; import android.app.DatePickerDialog; import android.content.Intent; import android.os.Bundle; import android.support.design.widget.TextInputEditText; import android.text.TextUtils; import android.view.View; import android.widget.Button; import android.widget.DatePicker; import android.widget.ImageView; import android.widget.TextView; import com.wj.android.todo.R; import com.wj.android.todo.activity.base.BaseActivity; import com.wj.android.todo.bean.TodoDesBean; import com.wj.android.todo.http.HttpUtils; import com.wj.android.todo.http.ResponseItem; import com.wj.android.todo.util.TimeUtils; import java.util.Calendar; import java.util.Date; import butterknife.BindView; import butterknife.OnClick; /** * 作者:wangwnejie on 2018/8/8 19:21 * 邮箱:wang20080990@163.com */ public class EditTodoActivity extends BaseActivity { @BindView(R.id.title) TextView mTitle; @BindView(R.id.back) ImageView mBack; @BindView(R.id.todo_date) TextView mTodoDate; @BindView(R.id.todo_name) TextInputEditText mTodoName; @BindView(R.id.todo_des) TextInputEditText mTodoDes; @BindView(R.id.save_todo) Button mSaveTodo; private TodoDesBean mTodoDesBean; @Override protected int getLayoutId() { return R.layout.activity_edit_todo; } @Override protected void initData() { mTitle.setText(R.string.update_todo); mBack.setVisibility(View.VISIBLE); Bundle bundle = getIntent().getExtras(); if (bundle == null) { return; } mTodoDesBean = (TodoDesBean) bundle.getSerializable("todo_des"); if (mTodoDesBean == null) { return; } mTodoName.setText(mTodoDesBean.getTitle()); if (!TextUtils.isEmpty(mTodoDesBean.getContent())) { mTodoDes.setText(mTodoDesBean.getContent()); } mTodoDate.setText(mTodoDesBean.getDateStr()); if (mTodoDesBean.getStatus() == 1) { mSaveTodo.setVisibility(View.GONE); mTodoName.setEnabled(false); mTodoName.setFocusable(false); mTodoDes.setEnabled(false); mTodoDes.setFocusable(false); mTodoDate.setEnabled(false); mTodoDate.setFocusable(false); } } @OnClick(R.id.back) void onClickBack() { finish(); } @OnClick(R.id.todo_date) void onClickTodoDate() { Calendar calendar = Calendar.getInstance(); DatePickerDialog datePickerDialog = new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() { @Override public void onDateSet(DatePicker datePicker, int year, int month, int dayOfMonth) { mTodoDate.setText(String.format("%d-%d-%d", year, month+1, dayOfMonth)); } },calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)); datePickerDialog.getDatePicker().setMinDate(new Date().getTime()); datePickerDialog.show(); } @OnClick(R.id.save_todo) void onClickAddTodo() { mTodoName.setError(null); if (TextUtils.isEmpty(mTodoName.getText())) { mTodoName.setError(getString(R.string.input_todo_name_toast)); mTodoName.setFocusable(true); mTodoName.setFocusableInTouchMode(true); mTodoName.requestFocus(); return; } requestUpdateTodoData(); } private void requestUpdateTodoData() { HttpUtils.requestUpdateTodoData(this, mTodoDesBean.getId(), mTodoName.getText().toString(), mTodoDes.getText().toString(), mTodoDate.getText().toString()); } public void updateUI(ResponseItem response) { if (response.isSuccess()) { showToast(getString(R.string.update_todo_success)); Intent intent = new Intent(); intent.putExtra("update_todo", response.getData()); setResult(0x210, intent); finish(); } } @Override public boolean isLoadingEnable(int requestId) { return super.isLoadingEnable(requestId); } } ================================================ FILE: app/src/main/java/com/wj/android/todo/activity/LoginActivity.java ================================================ package com.wj.android.todo.activity; import android.text.TextUtils; import android.widget.AutoCompleteTextView; import android.widget.EditText; import com.google.gson.JsonObject; import com.wj.android.todo.R; import com.wj.android.todo.activity.base.BaseActivity; import com.wj.android.todo.http.HttpUtils; import com.wj.android.todo.http.ResponseItem; import com.wj.android.todo.manager.SharePreferenceManager; import butterknife.BindView; import butterknife.OnClick; public class LoginActivity extends BaseActivity{ @BindView(R.id.account) AutoCompleteTextView mAccount; @BindView(R.id.password) EditText mPassword; @Override protected int getLayoutId() { return R.layout.activity_login; } @Override protected void initData() { } @OnClick(R.id.regitster) void clickRegister() { startActivity(RegisterActivity.class); } @OnClick(R.id.login) void clickLogin() { mAccount.setError(null); mPassword.setError(null); if (TextUtils.isEmpty(mAccount.getText())) { mAccount.setError(getString(R.string.input_account)); mAccount.setFocusable(true); mAccount.setFocusableInTouchMode(true); mAccount.requestFocus(); return; } if (TextUtils.isEmpty(mPassword.getText())) { mPassword.setError(getString(R.string.input_password)); mPassword.setFocusable(true); mPassword.setFocusableInTouchMode(true); mPassword.requestFocus(); return; } requestLogin(); } @Override public boolean isLoadingEnable(int requestId) { return super.isLoadingEnable(requestId); } private void requestLogin() { HttpUtils.requestLogin(this, mAccount.getText().toString(), mPassword.getText().toString()); } public void updateUI(ResponseItem response) { if (response.isSuccess()) { SharePreferenceManager.getInstance(this).setUserName(response.getData().get("username").getAsString()); startActivity(MainActivity.class); finish(); } } } ================================================ FILE: app/src/main/java/com/wj/android/todo/activity/MainActivity.java ================================================ package com.wj.android.todo.activity; import android.content.Intent; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import com.wj.android.todo.R; import com.wj.android.todo.activity.base.BaseActivity; import com.wj.android.todo.bean.TodoDesBean; import com.wj.android.todo.fragment.SettingFragment; import com.wj.android.todo.fragment.TodoFragment; import com.wj.android.todo.widget.BottomNavigationViewEx; import java.util.ArrayList; import java.util.List; import butterknife.BindView; import butterknife.OnClick; /** * 作者:wangwnejie on 2018/8/7 11:17 * 邮箱:wang20080990@163.com */ public class MainActivity extends BaseActivity { private static final int REQUEST_CODE_ADD_TODO = 0x100; @BindView(R.id.view_pager) ViewPager mViewPager; @BindView(R.id.navigation) BottomNavigationViewEx mNavigationView; @BindView(R.id.title) TextView mTitle; @BindView(R.id.add_todo) ImageView mAddTodo; @Override protected int getLayoutId() { return R.layout.activity_main; } @Override protected void initData() { mTitle.setText(R.string.to_do_list); mAddTodo.setVisibility(View.VISIBLE); mViewPager.setOffscreenPageLimit(3); List fragments = new ArrayList<>(3); fragments.add(TodoFragment.newInstance(false)); fragments.add(TodoFragment.newInstance(true)); fragments.add(SettingFragment.newInstance()); MainViewPagerAdapter adapter = new MainViewPagerAdapter(getSupportFragmentManager(), fragments); mViewPager.setAdapter(adapter); mNavigationView.setupWithViewPager(mViewPager); } @Override protected void applyEvent() { mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float v, int i1) { } @Override public void onPageSelected(int position) { switch (position) { case 0: mTitle.setText(R.string.to_do_list); mAddTodo.setVisibility(View.VISIBLE); break; case 1: mTitle.setText(R.string.complete_list); mAddTodo.setVisibility(View.INVISIBLE); break; case 2: mTitle.setText(R.string.setting); mAddTodo.setVisibility(View.INVISIBLE); break; } } @Override public void onPageScrollStateChanged(int position) { } }); } @OnClick(R.id.add_todo) void onClickAddTodo() { startActivityForResult(AddTodoActivity.class, REQUEST_CODE_ADD_TODO); } private static class MainViewPagerAdapter extends FragmentPagerAdapter { private List mFragments; public MainViewPagerAdapter(FragmentManager fm, List fragments) { super(fm); mFragments = fragments; } @Override public Fragment getItem(int position) { return mFragments.get(position); } @Override public int getCount() { return mFragments.size(); } } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_ADD_TODO) { switch (resultCode) { case 0x200: TodoDesBean todoDesBean = (TodoDesBean) data.getSerializableExtra("add_todo"); TodoFragment todoFragment = (TodoFragment)((MainViewPagerAdapter)mViewPager.getAdapter()).getItem(0); todoFragment.updateAddTodoData(todoDesBean); break; } } } public void updateDoneOrCancelData(TodoDesBean todoDesBean, int postition) { TodoFragment todoFragment = (TodoFragment)((MainViewPagerAdapter)mViewPager.getAdapter()).getItem(postition); todoFragment.updateAddTodoData(todoDesBean); } } ================================================ FILE: app/src/main/java/com/wj/android/todo/activity/RegisterActivity.java ================================================ package com.wj.android.todo.activity; import android.text.TextUtils; import android.view.View; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; import com.google.gson.JsonObject; import com.wj.android.todo.R; import com.wj.android.todo.activity.base.BaseActivity; import com.wj.android.todo.http.HttpUtils; import com.wj.android.todo.http.ResponseItem; import butterknife.BindView; import butterknife.OnClick; /** * 作者:wangwnejie on 2018/8/8 18:20 * 邮箱:wang20080990@163.com */ public class RegisterActivity extends BaseActivity { @BindView(R.id.title) TextView mTitle; @BindView(R.id.back) ImageView mBack; @BindView(R.id.account) EditText mAccount; @BindView(R.id.password) EditText mPassword; @BindView(R.id.repassword) EditText mRepassword; @Override protected int getLayoutId() { return R.layout.activity_register; } @Override protected void initData() { mTitle.setText(R.string.register); mBack.setVisibility(View.VISIBLE); } @OnClick(R.id.regitster) void onClickRegister() { mAccount.setError(null); mPassword.setError(null); if (TextUtils.isEmpty(mAccount.getText())) { mAccount.setError(getString(R.string.input_account)); mAccount.setFocusable(true); mAccount.setFocusableInTouchMode(true); mAccount.requestFocus(); return; } if (TextUtils.isEmpty(mPassword.getText())) { mPassword.setError(getString(R.string.input_password)); mPassword.setFocusable(true); mPassword.setFocusableInTouchMode(true); mPassword.requestFocus(); return; } if (TextUtils.isEmpty(mRepassword.getText())) { mRepassword.setError(getString(R.string.input_repassword)); mRepassword.setFocusable(true); mRepassword.setFocusableInTouchMode(true); mRepassword.requestFocus(); return; } if (!TextUtils.equals(mPassword.getText(), mRepassword.getText())) { mRepassword.setError(getString(R.string.valid_repassword)); return; } requestRegister(); } private void requestRegister() { HttpUtils.requestRegister(this, mAccount.getText().toString(), mPassword.getText().toString(), mRepassword.getText().toString()); } @OnClick(R.id.back) void onClickBack() { finish(); } public void updateUI(ResponseItem response) { if (response.isSuccess()) { showToast(getResources().getString(R.string.register_success)); finish(); } } @Override public boolean isLoadingEnable(int requestId) { return super.isLoadingEnable(requestId); } } ================================================ FILE: app/src/main/java/com/wj/android/todo/activity/SplashActivity.java ================================================ package com.wj.android.todo.activity; import android.os.Bundle; import android.view.View; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import com.wj.android.todo.BuildConfig; import com.wj.android.todo.R; import com.wj.android.todo.activity.base.BaseActivity; import com.wj.android.todo.constant.Constant; import com.wj.android.todo.manager.PersistentCookieJarManager; import java.util.List; import butterknife.BindView; import okhttp3.Cookie; import okhttp3.HttpUrl; /** * 作者:wangwnejie on 2018/8/8 16:01 * 邮箱:wang20080990@163.com */ public class SplashActivity extends BaseActivity { @BindView(R.id.root_view) View mView; @Override protected int getLayoutId() { return R.layout.activity_splash; } @Override protected void initData() { AlphaAnimation animation = new AlphaAnimation(0.3f, 1.0f); animation.setDuration(3000); mView.startAnimation(animation); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { redirectTo(); } @Override public void onAnimationRepeat(Animation animation) { } }); } private void redirectTo() { List cookies = PersistentCookieJarManager.getInstance(this).getPersistentCookieJar().loadForRequest(HttpUrl.parse(BuildConfig.BASE_URL)); if (cookies.isEmpty()) { startActivity(LoginActivity.class); } else { Bundle bundle = new Bundle(); bundle.putString("user_name", cookies.get(0).name()); startActivity(MainActivity.class, bundle); } finish(); } } ================================================ FILE: app/src/main/java/com/wj/android/todo/activity/base/BaseActivity.java ================================================ package com.wj.android.todo.activity.base; import android.app.ProgressDialog; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.widget.Toast; import com.wj.android.http.BaseView; import com.wj.android.todo.R; import com.wj.android.todo.exception.ApiException; import butterknife.ButterKnife; /** * 作者:wangwnejie on 2018/3/22 16:57 * 邮箱:wang20080990@163.com */ public abstract class BaseActivity extends AppCompatActivity implements BaseView { private static final String TAG = BaseActivity.class.getSimpleName(); private ProgressDialog mProgressDialog; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i(TAG, String.format("%s:onCreate", this)); if (getLayoutId() != 0) { setContentView(getLayoutId()); } ButterKnife.bind(this); initView(); initData(); applyEvent(); } @Override protected void onStart() { super.onStart(); Log.i(TAG, String.format("%s:onStart", this)); } @Override protected void onRestart() { super.onRestart(); Log.i(TAG, String.format("%s:onRestart", this)); } @Override protected void onResume() { super.onResume(); Log.i(TAG, String.format("%s:onResume", this)); } @Override protected void onPause() { super.onPause(); Log.i(TAG, String.format("%s:onPause", this)); } @Override protected void onStop() { super.onStop(); Log.i(TAG, String.format("%s:onStop", this)); } @Override protected void onDestroy() { super.onDestroy(); Log.i(TAG, String.format("%s:onDestroy", this)); if (mProgressDialog != null) { mProgressDialog.dismiss(); } } /** * 返回当前界面布局文件 * @return */ protected abstract int getLayoutId(); /** * 初始化View */ protected void initView(){} /** * 初始化数据 */ protected abstract void initData(); /** * 设置事件监听 */ protected void applyEvent(){} @Override public boolean isLoadingEnable(int requestId) { return false; } @Override public void start(int requestId) { if (isLoadingEnable(requestId) && !isFinishing()) { mProgressDialog = new ProgressDialog(this,ProgressDialog.THEME_HOLO_LIGHT); mProgressDialog.setCanceledOnTouchOutside(false); mProgressDialog.setCancelable(false); mProgressDialog.setMessage(getResources().getString(R.string.loading)); mProgressDialog.show(); } } @Override public void error(Throwable t, int code, int requestId) { if (t instanceof ApiException) { showToast(t.getMessage()); } else { showToast(getResources().getString(R.string.service_error)); } } @Override public void end(int requestId) { if (isLoadingEnable(requestId) && !isFinishing()) { mProgressDialog.dismiss(); } } public void showToast(String msg) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } public void startActivity(Class cls) { Intent intent = new Intent(this, cls); startActivity(intent); } public void startActivity(Class cls, Bundle bundle) { Intent intent = new Intent(this, cls); intent.putExtras(bundle); startActivity(intent); } public void startActivityForResult(Class cls, int requestCode) { Intent intent = new Intent(this, cls); startActivityForResult(intent, requestCode); } } ================================================ FILE: app/src/main/java/com/wj/android/todo/adapter/TodoSectionAdapter.java ================================================ package com.wj.android.todo.adapter; import android.text.TextUtils; import com.chad.library.adapter.base.BaseSectionQuickAdapter; import com.chad.library.adapter.base.BaseViewHolder; import com.wj.android.todo.R; import com.wj.android.todo.bean.TodoSection; import java.util.List; /** * 作者:wangwnejie on 2018/8/9 14:41 * 邮箱:wang20080990@163.com */ public class TodoSectionAdapter extends BaseSectionQuickAdapter { private boolean isDone; public TodoSectionAdapter(int layoutResId, int sectionHeadResId, List data, boolean isDone) { super(layoutResId, sectionHeadResId, data); this.isDone = isDone; } @Override protected void convertHead(BaseViewHolder helper, TodoSection item) { helper.setText(R.id.todo_head, item.header); if (isDone) { helper.setTextColor(R.id.todo_head, mContext.getResources().getColor(R.color.done_todo_date)); } else { helper.setTextColor(R.id.todo_head, mContext.getResources().getColor(R.color.colorPrimary)); } } @Override protected void convert(BaseViewHolder helper, TodoSection item) { helper.setText(R.id.item_name, item.t.getTitle()); if (TextUtils.isEmpty(item.t.getContent())) { helper.setGone(R.id.item_des, false); } else { helper.setGone(R.id.item_des, true); helper.setText(R.id.item_des, item.t.getContent()); } if (isDone) { helper.setGone(R.id.item_done_time, true); helper.setText(R.id.item_done_time, String.format(mContext.getResources().getString(R.string.done_todo_date),item.t.getCompleteDateStr())); helper.setImageResource(R.id.item_complete, R.drawable.cancel_todo); } else { helper.setGone(R.id.item_done_time, false); helper.setImageResource(R.id.item_complete, R.drawable.complete_todo); } helper.addOnClickListener(R.id.item_complete); helper.addOnClickListener(R.id.item_delete); } } ================================================ FILE: app/src/main/java/com/wj/android/todo/bean/TodoDesBean.java ================================================ package com.wj.android.todo.bean; import java.io.Serializable; /** * 作者:wangwnejie on 2018/8/9 14:29 * 邮箱:wang20080990@163.com */ public class TodoDesBean implements Serializable{ private Object completeDate; private String completeDateStr; private String content; private long date; private String dateStr; private int id; private int status; private String title; private int type; private int userId; public Object getCompleteDate() { return completeDate; } public void setCompleteDate(Object completeDate) { this.completeDate = completeDate; } public String getCompleteDateStr() { return completeDateStr; } public void setCompleteDateStr(String completeDateStr) { this.completeDateStr = completeDateStr; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public long getDate() { return date; } public void setDate(long date) { this.date = date; } public String getDateStr() { return dateStr; } public void setDateStr(String dateStr) { this.dateStr = dateStr; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public int getType() { return type; } public void setType(int type) { this.type = type; } public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } } ================================================ FILE: app/src/main/java/com/wj/android/todo/bean/TodoListBean.java ================================================ package com.wj.android.todo.bean; import java.util.List; /** * 作者:wangwnejie on 2018/8/9 14:55 * 邮箱:wang20080990@163.com */ public class TodoListBean { private int curPage; private int offset; private boolean over; private int pageCount; private int size; private int total; private List datas; public int getCurPage() { return curPage; } public void setCurPage(int curPage) { this.curPage = curPage; } public int getOffset() { return offset; } public void setOffset(int offset) { this.offset = offset; } public boolean isOver() { return over; } public void setOver(boolean over) { this.over = over; } public int getPageCount() { return pageCount; } public void setPageCount(int pageCount) { this.pageCount = pageCount; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public int getTotal() { return total; } public void setTotal(int total) { this.total = total; } public List getDatas() { return datas; } public void setDatas(List datas) { this.datas = datas; } } ================================================ FILE: app/src/main/java/com/wj/android/todo/bean/TodoSection.java ================================================ package com.wj.android.todo.bean; import com.chad.library.adapter.base.entity.SectionEntity; /** * 作者:wangwnejie on 2018/8/9 14:33 * 邮箱:wang20080990@163.com */ public class TodoSection extends SectionEntity { public TodoSection(boolean isHeader, String header) { super(isHeader, header); } public TodoSection(TodoDesBean todoBean) { super(todoBean); } } ================================================ FILE: app/src/main/java/com/wj/android/todo/constant/Constant.java ================================================ package com.wj.android.todo.constant; /** * 作者:wangwnejie on 2018/8/7 15:24 * 邮箱:wang20080990@163.com */ public class Constant { //public static final String BASE_URL = "http://www.wanandroid.com"; public static final String LOGIN_URI = "/user/login"; public static final String REGISTER_URI = "/user/register"; public static final String ADD_TODO_URI = "/lg/todo/add/json"; public static final String TODO_LIST_URI = "/lg/todo/listnotdo/0/json/%d"; public static final String DELETE_TODO_URI = "/lg/todo/delete/%d/json"; public static final String UPDATE_TODO_URI = "/lg/todo/update/%d/json"; public static final String DONE_TODO_URI = "/lg/todo/done/%d/json"; public static final String DONE_LIST_URI = "/lg/todo/listdone/0/json/%d"; } ================================================ FILE: app/src/main/java/com/wj/android/todo/constant/TimeConstants.java ================================================ package com.wj.android.todo.constant; import android.support.annotation.IntDef; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** *
 *     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 response) { if (response.isSuccess() && response.getData() != null) { if (page == 1) { mTodoListBean = response.getData(); mAdapter = new TodoSectionAdapter(R.layout.todo_item_view,R.layout.todo_item_head,getTodoSectionData(mTodoListBean.getDatas()),isDone); mAdapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() { @Override public void onLoadMoreRequested() { if (mTodoListBean.getPageCount() == 1) { mAdapter.loadMoreEnd(true); } else if (page >= mTodoListBean.getPageCount()) { mAdapter.loadMoreEnd(false); } else { page++; requestTodoListData(); } } }, mRecyclerView); mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() { @Override public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) { switch (view.getId()){ case R.id.item_complete: donePosition = position; doneTodoById(mAdapter.getData().get(position).t.getId()); break; case R.id.item_delete: showDialog(position); break; } } }); mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { @Override public void onItemClick(BaseQuickAdapter adapter, View view, int position) { TodoSection todoSection = mAdapter.getData().get(position); //showTodoDes(todoSection); Bundle bundle = new Bundle(); bundle.putSerializable("todo_des", todoSection.t); startActivityForResult(EditTodoActivity.class, bundle, REQUEST_CODE_EDIT_TODO); } }); mAdapter.openLoadAnimation(BaseQuickAdapter.SLIDEIN_BOTTOM); mRecyclerView.setAdapter(mAdapter); } else { mAdapter.addData(getTodoSectionData(response.getData().getDatas())); mAdapter.loadMoreComplete(); } } } private void showTodoDes(TodoSection todoSection) { View view = View.inflate(getContext(),R.layout.dialog_todo_des_view, null); TextView todoName = view.findViewById(R.id.todo_name); todoName.setText(todoSection.t.getTitle()); TextView todoContent = view.findViewById(R.id.todo_content); if (TextUtils.isEmpty(todoSection.t.getContent())) { todoContent.setText(R.string.no_text); } else { todoContent.setText(todoSection.t.getContent()); } BottomSheetDialog sheetDialog = new BottomSheetDialog(getContext()); sheetDialog.setContentView(view); sheetDialog.show(); } private List getTodoSectionData(List datas) { List todoSections = new ArrayList<>(); LinkedHashSet dates = new LinkedHashSet<>(); for (TodoDesBean todoDesBean : datas) { dates.add(todoDesBean.getDateStr()); } for (String date : dates) { TodoSection todoSectionHead = new TodoSection(true, date); todoSections.add(todoSectionHead); for (TodoDesBean todoDesBean : datas) { if (TextUtils.equals(date, todoDesBean.getDateStr())) { TodoSection todoSectionContent = new TodoSection(todoDesBean); todoSections.add(todoSectionContent); } } } return todoSections; } @Override public boolean isLoadingEnable(int requestId) { return requestId == 1; } @Override public void start(int requestId) { if (requestId == 1) { super.start(requestId); } else { if (page == 1) { mSwipeRefreshLayout.setRefreshing(true); } } } @Override public void error(Throwable t, int code, int requestId) { super.error(t,code,requestId); if (requestId == 0) { if (page > 1) { mAdapter.loadMoreFail(); } } } @Override public void end(int requestId) { if (requestId == 1) { super.end(requestId); } else { if (page == 1) { mSwipeRefreshLayout.setRefreshing(false); } } } public void updateAddTodoData(TodoDesBean todoDesBean) { List todoSections = mAdapter.getData(); for (int i = 0; i < todoSections.size(); i++) { TodoSection todoSection = todoSections.get(i); if (todoSection.isHeader && TextUtils.equals(todoSection.header, todoDesBean.getDateStr())) { TodoSection section = new TodoSection(todoDesBean); mAdapter.getData().add(i+1,section); mAdapter.notifyItemInserted(i+1); mRecyclerView.scrollToPosition(i+1); return; } } TodoSection sectionHead = new TodoSection(true,todoDesBean.getDateStr()); mAdapter.getData().add(0, sectionHead); TodoSection section = new TodoSection(todoDesBean); mAdapter.getData().add(1, section); mAdapter.notifyItemRangeInserted(0,2); mRecyclerView.scrollToPosition(0); } public void updateRemovedData(ResponseItem response) { if (response.isSuccess() && deletePosition != -1) { if (mAdapter.getData().get(deletePosition-1).isHeader && (mAdapter.getData().size()== deletePosition+2 || mAdapter.getData().get(deletePosition+1).isHeader)) { mAdapter.getData().remove(deletePosition-1); mAdapter.getData().remove(deletePosition-1); mAdapter.notifyItemRangeRemoved(deletePosition-1,2); } else { mAdapter.getData().remove(deletePosition); mAdapter.notifyItemRemoved(deletePosition); } showToast(getString(R.string.delete_todo_success)); } } public void updateDoneData(ResponseItem response) { if (response.isSuccess() && donePosition != -1) { if (mAdapter.getData().get(donePosition-1).isHeader && (mAdapter.getData().size()== donePosition+2 || mAdapter.getData().get(donePosition+1).isHeader)) { mAdapter.getData().remove(donePosition-1); mAdapter.getData().remove(donePosition-1); mAdapter.notifyItemRangeRemoved(donePosition-1,2); } else { mAdapter.getData().remove(donePosition); mAdapter.notifyItemRemoved(donePosition); } showToast(getString(isDone ? R.string.notdo_todo_success : R.string.done_todo_success)); ((MainActivity)getActivity()).updateDoneOrCancelData(response.getData(), isDone ? 0 : 1); } } private void showDialog(final int position) { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); builder.setTitle(R.string.delete_todo); builder.setMessage(R.string.sure_delete_todo); builder.setNegativeButton(R.string.cancel,null); builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { deletePosition = position; deleteTodoById(mAdapter.getData().get(position).t.getId()); } }); builder.show(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_EDIT_TODO) { switch (resultCode) { case 0x210: page = 1; requestTodoListData(); break; } } } } ================================================ FILE: app/src/main/java/com/wj/android/todo/fragment/base/BaseFragment.java ================================================ package com.wj.android.todo.fragment.base; import android.app.ProgressDialog; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; import com.wj.android.http.BaseView; import com.wj.android.todo.R; import com.wj.android.todo.exception.ApiException; import butterknife.ButterKnife; import butterknife.Unbinder; /** * 作者:wangwnejie on 2018/4/2 16:58 * 邮箱:wang20080990@163.com */ public abstract class BaseFragment extends Fragment implements BaseView { private Unbinder mUnbinder; private ProgressDialog mProgressDialog; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(getLayoutId(), container, false); mUnbinder = ButterKnife.bind(this,view); return view; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); initData(); } @Override public void onDestroyView() { super.onDestroyView(); mUnbinder.unbind(); } /** * 返回当前界面布局文件 * @return */ protected abstract int getLayoutId(); /** * 初始化数据 */ protected abstract void initData(); public boolean isLoadingEnable(int requestId) { return false; } @Override public void start(int requestId) { if (isLoadingEnable(requestId) && !getActivity().isFinishing()) { mProgressDialog = new ProgressDialog(getContext(),ProgressDialog.THEME_HOLO_LIGHT); mProgressDialog.setCanceledOnTouchOutside(false); mProgressDialog.setCancelable(false); mProgressDialog.setMessage(getResources().getString(R.string.loading)); mProgressDialog.show(); } } @Override public void error(Throwable t, int code, int requestId) { if (isAdded()) { if (t instanceof ApiException) { showToast(t.getMessage()); } else { showToast(getResources().getString(R.string.service_error)); } } } @Override public void end(int requestId) { if (isLoadingEnable(requestId) && !getActivity().isFinishing()) { mProgressDialog.dismiss(); } } public void showToast(String msg) { Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show(); } public void startActivity(Class cls) { Intent intent = new Intent(getActivity(), cls); startActivity(intent); } public void startActivity(Class cls, Bundle bundle) { Intent intent = new Intent(getActivity(), cls); intent.putExtras(bundle); startActivity(intent); } public void startActivityForResult(Class cls, Bundle bundle,int requestCode) { Intent intent = new Intent(getActivity(), cls); intent.putExtras(bundle); startActivityForResult(intent, requestCode); } } ================================================ FILE: app/src/main/java/com/wj/android/todo/http/HttpUtils.java ================================================ package com.wj.android.todo.http; import android.text.TextUtils; import com.google.gson.JsonObject; import com.wj.android.http.BaseView; import com.wj.android.http.XRetrofit; import com.wj.android.todo.BuildConfig; import com.wj.android.todo.activity.AddTodoActivity; import com.wj.android.todo.activity.EditTodoActivity; import com.wj.android.todo.activity.LoginActivity; import com.wj.android.todo.activity.RegisterActivity; import com.wj.android.todo.bean.TodoDesBean; import com.wj.android.todo.bean.TodoListBean; import com.wj.android.todo.constant.Constant; import com.wj.android.todo.fragment.TodoFragment; import java.util.HashMap; import java.util.Map; /** * 作者:wangwnejie on 2018/8/7 15:21 * 邮箱:wang20080990@163.com */ public class HttpUtils { private static String buildUrl(String uri) { return String.format("%s%s", BuildConfig.BASE_URL, uri); } public static void requestLogin(BaseView baseView, String userName, String password){ Map params = new HashMap<>(); params.put("username", userName); params.put("password", password); XRetrofit.post(buildUrl(Constant.LOGIN_URI), params, new MyGsonCallback>(baseView) { @Override protected void onSuccess(ResponseItem response, BaseView baseView) { ((LoginActivity)baseView).updateUI(response); } }); } public static void requestRegister(BaseView baseView, String userName, String password, String repassword) { Map params = new HashMap<>(); params.put("username", userName); params.put("password", password); params.put("repassword", repassword); XRetrofit.post(buildUrl(Constant.REGISTER_URI), params, new MyGsonCallback>(baseView) { @Override protected void onSuccess(ResponseItem response, BaseView baseView) { ((RegisterActivity)baseView).updateUI(response); } }); } public static void requestAddTodoData(BaseView baseView, String todoName, String todoDes, String todoDate) { Map params = new HashMap<>(); params.put("title", todoName); if (!TextUtils.isEmpty(todoDes)) { params.put("content", todoDes); } params.put("date", todoDate); params.put("type", "0"); XRetrofit.post(buildUrl(Constant.ADD_TODO_URI), params, new MyGsonCallback>(baseView) { @Override protected void onSuccess(ResponseItem response, BaseView baseView) { ((AddTodoActivity)baseView).updateUI(response); } }); } public static void requestTodoList(BaseView baseView, int page, boolean isDone) { XRetrofit.post(isDone ? buildUrl(String.format(Constant.DONE_LIST_URI, page)) : buildUrl(String.format(Constant.TODO_LIST_URI, page)), new MyGsonCallback>(baseView) { @Override protected void onSuccess(ResponseItem response, BaseView baseView) { ((TodoFragment)baseView).updateUI(response); } }); } public static void deleteTodoById(BaseView baseView, int todoId) { XRetrofit.post(buildUrl(String.format(Constant.DELETE_TODO_URI, todoId)), new MyGsonCallback(baseView,1) { @Override protected void onSuccess(ResponseItem response, BaseView baseView) { ((TodoFragment)baseView).updateRemovedData(response); } }); } public static void updateTodoById(BaseView baseView, int todoId, String title, String content, String date) { } public static void doneTodoById(BaseView baseView, int todoId, int status) { Map params = new HashMap<>(); params.put("status", String.valueOf(status)); XRetrofit.post(buildUrl(String.format(Constant.DONE_TODO_URI, todoId)),params, new MyGsonCallback>(baseView,1) { @Override protected void onSuccess(ResponseItem response, BaseView baseView) { ((TodoFragment)baseView).updateDoneData(response); } }); } public static void requestUpdateTodoData(BaseView baseView, int todoId, String todoName, String todoDes, String todoDate) { Map params = new HashMap<>(); params.put("title", todoName); if (!TextUtils.isEmpty(todoDes)) { params.put("content", todoDes); } params.put("date", todoDate); XRetrofit.post(buildUrl(String.format(Constant.UPDATE_TODO_URI, todoId)), params, new MyGsonCallback>(baseView) { @Override protected void onSuccess(ResponseItem response, BaseView baseView) { ((EditTodoActivity)baseView).updateUI(response); } }); } } ================================================ FILE: app/src/main/java/com/wj/android/todo/http/MyGsonCallback.java ================================================ package com.wj.android.todo.http; import com.google.gson.Gson; import com.wj.android.http.BaseView; import com.wj.android.http.GsonCallback; import com.wj.android.todo.exception.ApiException; /** * 作者:wangwnejie on 2018/8/8 13:57 * 邮箱:wang20080990@163.com */ public abstract class MyGsonCallback extends GsonCallback { public MyGsonCallback(BaseView baseView) { super(baseView); } public MyGsonCallback(BaseView baseView, int requestId) { super(baseView, requestId); } @Override protected String convertResponse(String response) { ResponseItem responseItem = new Gson().fromJson(response, ResponseItem.class); if (!responseItem.isSuccess()) { throw new ApiException(responseItem.getErrorMsg(), responseItem.getErrorCode()); } return super.convertResponse(response); } } ================================================ FILE: app/src/main/java/com/wj/android/todo/http/ResponseItem.java ================================================ package com.wj.android.todo.http; import java.io.Serializable; /** * 响应类型 * @author wangwenjie *泛型T是实际的响应类型 *响应类型T为Object */ public class ResponseItem implements Serializable { /** * 错误的内部编号,success表示成功 */ private int errorCode; /** * 错误描述 */ private String errorMsg; /** * 返回值 */ private T data; public int getErrorCode() { return errorCode; } public void setErrorCode(int errorCode) { this.errorCode = errorCode; } public String getErrorMsg() { return errorMsg; } public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } public T getData() { return data; } public void setData(T data) { this.data = data; } public boolean isSuccess() { return errorCode == 0; } } ================================================ FILE: app/src/main/java/com/wj/android/todo/manager/PersistentCookieJarManager.java ================================================ package com.wj.android.todo.manager; import android.content.Context; import com.franmontiel.persistentcookiejar.PersistentCookieJar; import com.franmontiel.persistentcookiejar.cache.SetCookieCache; import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; import com.wj.android.http.RetrofitHttpManager; /** * 作者:wangwnejie on 2018/8/8 16:34 * 邮箱:wang20080990@163.com */ public class PersistentCookieJarManager { private volatile static PersistentCookieJarManager sInstance; private PersistentCookieJar mPersistentCookieJar; private PersistentCookieJarManager(Context context) { mPersistentCookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context)); } public static PersistentCookieJarManager getInstance(Context context) { if (sInstance == null) { synchronized (RetrofitHttpManager.class) { if (sInstance == null) { sInstance = new PersistentCookieJarManager(context.getApplicationContext()); } } } return sInstance; } public PersistentCookieJar getPersistentCookieJar() { return mPersistentCookieJar; } } ================================================ FILE: app/src/main/java/com/wj/android/todo/manager/PreferenceHelper.java ================================================ /******************************************************************************* * Copyright (c) Baina Info Tech Co. Ltd *

* DolphinCoreLibrary_Webzine *

* PreferenceHelper * TODO File description or class description. * * @author: dhu * @since: 2011-8-6 * @version: 1.0 ******************************************************************************/ package com.wj.android.todo.manager; import android.annotation.SuppressLint; import android.content.SharedPreferences.Editor; import android.os.Build; /** * PreferenceHelper of DolphinCoreLibrary_Webzine. * * @author dhu */ public abstract class PreferenceHelper { public abstract void save(Editor editor); private static PreferenceHelper sHelper; public static PreferenceHelper getInstance() { if (null == sHelper) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { sHelper = new NewPreferenceHelper(); } else { sHelper = new OlderPreferenceHelper(); } } return sHelper; } private static class OlderPreferenceHelper extends PreferenceHelper { @Override public void save(Editor editor) { editor.commit(); } } private static class NewPreferenceHelper extends PreferenceHelper { @SuppressLint("NewApi") @Override public void save(Editor editor) { editor.apply(); } } } ================================================ FILE: app/src/main/java/com/wj/android/todo/manager/SharePreferenceManager.java ================================================ package com.wj.android.todo.manager; import android.content.Context; import android.content.SharedPreferences; /** * Created by Administrator on 2017/5/25. */ public class SharePreferenceManager { private static final String PREF_FILE = "wj_todo"; private static final String KEY_USER_NAME = "user_name"; private volatile static SharePreferenceManager sInstance; private SharedPreferences mSharedPreferences; private SharePreferenceManager(Context context) { mSharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); } public static SharePreferenceManager getInstance(Context context) { if (sInstance == null) { synchronized (SharePreferenceManager.class) { if (sInstance == null) { sInstance = new SharePreferenceManager(context.getApplicationContext()); } } } return sInstance; } public void setUserName(String userName) { SharedPreferences.Editor editor = mSharedPreferences.edit(); editor.putString(KEY_USER_NAME, userName); PreferenceHelper.getInstance().save(editor); } public String getUserName() { return mSharedPreferences.getString(KEY_USER_NAME, null); } public void clear() { mSharedPreferences.edit().clear(); } } ================================================ FILE: app/src/main/java/com/wj/android/todo/util/TimeUtils.java ================================================ package com.wj.android.todo.util; import android.support.annotation.NonNull; import com.wj.android.todo.constant.TimeConstants; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; /** *

 *     author: Blankj
 *     blog  : http://blankj.com
 *     time  : 2016/08/02
 *     desc  : utils about time
 * 
*/ public final class TimeUtils { private static final ThreadLocal SDF_THREAD_LOCAL = new ThreadLocal<>(); private static SimpleDateFormat getDefaultFormat() { SimpleDateFormat simpleDateFormat = SDF_THREAD_LOCAL.get(); if (simpleDateFormat == null) { simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); SDF_THREAD_LOCAL.set(simpleDateFormat); } return simpleDateFormat; } private static SimpleDateFormat getDateFormat(String pattern) { return new SimpleDateFormat(pattern, Locale.getDefault()); } private TimeUtils() { throw new UnsupportedOperationException("u can't instantiate me..."); } /** * Milliseconds to the formatted time string. *

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. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the time span, in unit */ public static long getTimeSpan(final String time1, final String time2, @TimeConstants.Unit final int unit) { return getTimeSpan(time1, time2, getDefaultFormat(), unit); } /** * Return the time span, in unit. * * @param time1 The first formatted time string. * @param time2 The second formatted time string. * @param format The format. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the time span, in unit */ public static long getTimeSpan(final String time1, final String time2, @NonNull final DateFormat format, @TimeConstants.Unit final int unit) { return millis2TimeSpan(string2Millis(time1, format) - string2Millis(time2, format), unit); } /** * Return the time span, in unit. * * @param date1 The first date. * @param date2 The second date. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the time span, in unit */ public static long getTimeSpan(final Date date1, final Date date2, @TimeConstants.Unit final int unit) { return millis2TimeSpan(date2Millis(date1) - date2Millis(date2), unit); } /** * Return the time span, in unit. * * @param millis1 The first milliseconds. * @param millis2 The second milliseconds. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the time span, in unit */ public static long getTimeSpan(final long millis1, final long millis2, @TimeConstants.Unit final int unit) { return millis2TimeSpan(millis1 - millis2, unit); } /** * Return the fit 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. *
    *
  • precision = 0, return null
  • *
  • precision = 1, return 天
  • *
  • precision = 2, return 天, 小时
  • *
  • precision = 3, return 天, 小时, 分钟
  • *
  • precision = 4, return 天, 小时, 分钟, 秒
  • *
  • precision >= 5,return 天, 小时, 分钟, 秒, 毫秒
  • *
* @return the fit time span */ public static String getFitTimeSpan(final String time1, final String time2, final int precision) { long delta = string2Millis(time1, getDefaultFormat()) - string2Millis(time2, getDefaultFormat()); return millis2FitTimeSpan(delta, precision); } /** * Return the fit time span. * * @param time1 The first formatted time string. * @param time2 The second formatted time string. * @param format The format. * @param precision The precision of time span. *
    *
  • precision = 0, return null
  • *
  • precision = 1, return 天
  • *
  • precision = 2, return 天, 小时
  • *
  • precision = 3, return 天, 小时, 分钟
  • *
  • precision = 4, return 天, 小时, 分钟, 秒
  • *
  • precision >= 5,return 天, 小时, 分钟, 秒, 毫秒
  • *
* @return the fit time span */ public static String getFitTimeSpan(final String time1, final String time2, @NonNull final DateFormat format, final int precision) { long delta = string2Millis(time1, format) - string2Millis(time2, format); return millis2FitTimeSpan(delta, precision); } /** * Return the fit time span. * * @param date1 The first date. * @param date2 The second date. * @param precision The precision of time span. *
    *
  • precision = 0, return null
  • *
  • precision = 1, return 天
  • *
  • precision = 2, return 天, 小时
  • *
  • precision = 3, return 天, 小时, 分钟
  • *
  • precision = 4, return 天, 小时, 分钟, 秒
  • *
  • precision >= 5,return 天, 小时, 分钟, 秒, 毫秒
  • *
* @return the fit time span */ public static String getFitTimeSpan(final Date date1, final Date date2, final int precision) { return millis2FitTimeSpan(date2Millis(date1) - date2Millis(date2), precision); } /** * Return the fit time span. * * @param millis1 The first milliseconds. * @param millis2 The second milliseconds. * @param precision The precision of time span. *
    *
  • precision = 0, return null
  • *
  • precision = 1, return 天
  • *
  • precision = 2, return 天, 小时
  • *
  • precision = 3, return 天, 小时, 分钟
  • *
  • precision = 4, return 天, 小时, 分钟, 秒
  • *
  • precision >= 5,return 天, 小时, 分钟, 秒, 毫秒
  • *
* @return the fit time span */ public static String getFitTimeSpan(final long millis1, final long millis2, final int precision) { return millis2FitTimeSpan(millis1 - millis2, precision); } /** * Return the current time in milliseconds. * * @return the current time in milliseconds */ public static long getNowMills() { return System.currentTimeMillis(); } /** * Return the current formatted time string. *

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. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the time span by now, in unit */ public static long getTimeSpanByNow(final String time, @TimeConstants.Unit final int unit) { return getTimeSpan(time, getNowString(), getDefaultFormat(), unit); } /** * Return the time span by now, in unit. * * @param time The formatted time string. * @param format The format. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the time span by now, in unit */ public static long getTimeSpanByNow(final String time, @NonNull final DateFormat format, @TimeConstants.Unit final int unit) { return getTimeSpan(time, getNowString(format), format, unit); } /** * Return the time span by now, in unit. * * @param date The date. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the time span by now, in unit */ public static long getTimeSpanByNow(final Date date, @TimeConstants.Unit final int unit) { return getTimeSpan(date, new Date(), unit); } /** * Return the time span by now, in unit. * * @param millis The milliseconds. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the time span by now, in unit */ public static long getTimeSpanByNow(final long millis, @TimeConstants.Unit final int unit) { return getTimeSpan(millis, System.currentTimeMillis(), unit); } /** * Return the fit time span by now. *

The pattern is {@code yyyy-MM-dd HH:mm:ss}.

* * @param time The formatted time string. * @param precision The precision of time span. *
    *
  • precision = 0,返回 null
  • *
  • precision = 1,返回天
  • *
  • precision = 2,返回天和小时
  • *
  • precision = 3,返回天、小时和分钟
  • *
  • precision = 4,返回天、小时、分钟和秒
  • *
  • precision >= 5,返回天、小时、分钟、秒和毫秒
  • *
* @return the fit time span by now */ public static String getFitTimeSpanByNow(final String time, final int precision) { return getFitTimeSpan(time, getNowString(), getDefaultFormat(), precision); } /** * Return the fit time span by now. * * @param time The formatted time string. * @param format The format. * @param precision The precision of time span. *
    *
  • precision = 0,返回 null
  • *
  • precision = 1,返回天
  • *
  • precision = 2,返回天和小时
  • *
  • precision = 3,返回天、小时和分钟
  • *
  • precision = 4,返回天、小时、分钟和秒
  • *
  • precision >= 5,返回天、小时、分钟、秒和毫秒
  • *
* @return the fit time span by now */ public static String getFitTimeSpanByNow(final String time, @NonNull final DateFormat format, final int precision) { return getFitTimeSpan(time, getNowString(format), format, precision); } /** * Return the fit time span by now. * * @param date The date. * @param precision The precision of time span. *
    *
  • precision = 0,返回 null
  • *
  • precision = 1,返回天
  • *
  • precision = 2,返回天和小时
  • *
  • precision = 3,返回天、小时和分钟
  • *
  • precision = 4,返回天、小时、分钟和秒
  • *
  • precision >= 5,返回天、小时、分钟、秒和毫秒
  • *
* @return the fit time span by now */ public static String getFitTimeSpanByNow(final Date date, final int precision) { return getFitTimeSpan(date, getNowDate(), precision); } /** * Return the fit time span by now. * * @param millis The milliseconds. * @param precision The precision of time span. *
    *
  • precision = 0,返回 null
  • *
  • precision = 1,返回天
  • *
  • precision = 2,返回天和小时
  • *
  • precision = 3,返回天、小时和分钟
  • *
  • precision = 4,返回天、小时、分钟和秒
  • *
  • precision >= 5,返回天、小时、分钟、秒和毫秒
  • *
* @return the fit time span by now */ public static String getFitTimeSpanByNow(final long millis, final int precision) { return getFitTimeSpan(millis, System.currentTimeMillis(), precision); } /** * Return the friendly time span by now. *

The pattern is {@code yyyy-MM-dd HH:mm:ss}.

* * @param time The formatted time string. * @return the friendly time span by now *
    *
  • 如果小于 1 秒钟内,显示刚刚
  • *
  • 如果在 1 分钟内,显示 XXX秒前
  • *
  • 如果在 1 小时内,显示 XXX分钟前
  • *
  • 如果在 1 小时外的今天内,显示今天15:32
  • *
  • 如果是昨天的,显示昨天15:32
  • *
  • 其余显示,2016-10-15
  • *
  • 时间不合法的情况全部日期和时间信息,如星期六 十月 27 14:21:20 CST 2007
  • *
*/ public static String getFriendlyTimeSpanByNow(final String time) { return getFriendlyTimeSpanByNow(time, getDefaultFormat()); } /** * Return the friendly time span by now. * * @param time The formatted time string. * @param format The format. * @return the friendly time span by now *
    *
  • 如果小于 1 秒钟内,显示刚刚
  • *
  • 如果在 1 分钟内,显示 XXX秒前
  • *
  • 如果在 1 小时内,显示 XXX分钟前
  • *
  • 如果在 1 小时外的今天内,显示今天15:32
  • *
  • 如果是昨天的,显示昨天15:32
  • *
  • 其余显示,2016-10-15
  • *
  • 时间不合法的情况全部日期和时间信息,如星期六 十月 27 14:21:20 CST 2007
  • *
*/ public static String getFriendlyTimeSpanByNow(final String time, @NonNull final DateFormat format) { return getFriendlyTimeSpanByNow(string2Millis(time, format)); } /** * Return the friendly time span by now. * * @param date The date. * @return the friendly time span by now *
    *
  • 如果小于 1 秒钟内,显示刚刚
  • *
  • 如果在 1 分钟内,显示 XXX秒前
  • *
  • 如果在 1 小时内,显示 XXX分钟前
  • *
  • 如果在 1 小时外的今天内,显示今天15:32
  • *
  • 如果是昨天的,显示昨天15:32
  • *
  • 其余显示,2016-10-15
  • *
  • 时间不合法的情况全部日期和时间信息,如星期六 十月 27 14:21:20 CST 2007
  • *
*/ public static String getFriendlyTimeSpanByNow(final Date date) { return getFriendlyTimeSpanByNow(date.getTime()); } /** * Return the friendly time span by now. * * @param millis The milliseconds. * @return the friendly time span by now *
    *
  • 如果小于 1 秒钟内,显示刚刚
  • *
  • 如果在 1 分钟内,显示 XXX秒前
  • *
  • 如果在 1 小时内,显示 XXX分钟前
  • *
  • 如果在 1 小时外的今天内,显示今天15:32
  • *
  • 如果是昨天的,显示昨天15:32
  • *
  • 其余显示,2016-10-15
  • *
  • 时间不合法的情况全部日期和时间信息,如星期六 十月 27 14:21:20 CST 2007
  • *
*/ public static String getFriendlyTimeSpanByNow(final long millis) { long now = System.currentTimeMillis(); long span = now - millis; if (span < 0) // U can read http://www.apihome.cn/api/java/Formatter.html to understand it. return String.format("%tc", millis); if (span < 1000) { return "刚刚"; } else if (span < TimeConstants.MIN) { return String.format(Locale.getDefault(), "%d秒前", span / TimeConstants.SEC); } else if (span < TimeConstants.HOUR) { return String.format(Locale.getDefault(), "%d分钟前", span / TimeConstants.MIN); } // 获取当天 00:00 long wee = getWeeOfToday(); if (millis >= wee) { return String.format("今天%tR", millis); } else if (millis >= wee - TimeConstants.DAY) { return String.format("昨天%tR", millis); } else { return String.format("%tF", millis); } } private static long getWeeOfToday() { Calendar cal = Calendar.getInstance(); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.MILLISECOND, 0); return cal.getTimeInMillis(); } /** * Return the milliseconds differ time span. * * @param millis The milliseconds. * @param timeSpan The time span. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the milliseconds differ time span */ public static long getMillis(final long millis, final long timeSpan, @TimeConstants.Unit final int unit) { return millis + timeSpan2Millis(timeSpan, unit); } /** * Return the milliseconds differ 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. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the milliseconds differ time span */ public static long getMillis(final String time, final long timeSpan, @TimeConstants.Unit final int unit) { return getMillis(time, getDefaultFormat(), timeSpan, unit); } /** * Return the milliseconds differ time span. * * @param time The formatted time string. * @param format The format. * @param timeSpan The time span. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the milliseconds differ time span. */ public static long getMillis(final String time, @NonNull final DateFormat format, final long timeSpan, @TimeConstants.Unit final int unit) { return string2Millis(time, format) + timeSpan2Millis(timeSpan, unit); } /** * Return the milliseconds differ time span. * * @param date The date. * @param timeSpan The time span. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the milliseconds differ time span. */ public static long getMillis(final Date date, final long timeSpan, @TimeConstants.Unit final int unit) { return date2Millis(date) + timeSpan2Millis(timeSpan, unit); } /** * Return the formatted time string differ 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. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the formatted time string differ time span */ public static String getString(final long millis, final long timeSpan, @TimeConstants.Unit final int unit) { return getString(millis, getDefaultFormat(), timeSpan, unit); } /** * Return the formatted time string differ time span. * * @param millis The milliseconds. * @param format The format. * @param timeSpan The time span. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the formatted time string differ time span */ public static String getString(final long millis, @NonNull final DateFormat format, final long timeSpan, @TimeConstants.Unit final int unit) { return millis2String(millis + timeSpan2Millis(timeSpan, unit), format); } /** * Return the formatted time string differ 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. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the formatted time string differ time span */ public static String getString(final String time, final long timeSpan, @TimeConstants.Unit final int unit) { return getString(time, getDefaultFormat(), timeSpan, unit); } /** * Return the formatted time string differ time span. * * @param time The formatted time string. * @param format The format. * @param timeSpan The time span. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the formatted time string differ time span */ public static String getString(final String time, @NonNull final DateFormat format, final long timeSpan, @TimeConstants.Unit final int unit) { return millis2String(string2Millis(time, format) + timeSpan2Millis(timeSpan, unit), format); } /** * Return the formatted time string differ 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. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the formatted time string differ time span */ public static String getString(final Date date, final long timeSpan, @TimeConstants.Unit final int unit) { return getString(date, getDefaultFormat(), timeSpan, unit); } /** * Return the formatted time string differ time span. * * @param date The date. * @param format The format. * @param timeSpan The time span. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the formatted time string differ time span */ public static String getString(final Date date, @NonNull final DateFormat format, final long timeSpan, @TimeConstants.Unit final int unit) { return millis2String(date2Millis(date) + timeSpan2Millis(timeSpan, unit), format); } /** * Return the date differ time span. * * @param millis The milliseconds. * @param timeSpan The time span. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the date differ time span */ public static Date getDate(final long millis, final long timeSpan, @TimeConstants.Unit final int unit) { return millis2Date(millis + timeSpan2Millis(timeSpan, unit)); } /** * Return the date differ 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. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the date differ time span */ public static Date getDate(final String time, final long timeSpan, @TimeConstants.Unit final int unit) { return getDate(time, getDefaultFormat(), timeSpan, unit); } /** * Return the date differ time span. * * @param time The formatted time string. * @param format The format. * @param timeSpan The time span. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the date differ time span */ public static Date getDate(final String time, @NonNull final DateFormat format, final long timeSpan, @TimeConstants.Unit final int unit) { return millis2Date(string2Millis(time, format) + timeSpan2Millis(timeSpan, unit)); } /** * Return the date differ time span. * * @param date The date. * @param timeSpan The time span. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the date differ time span */ public static Date getDate(final Date date, final long timeSpan, @TimeConstants.Unit final int unit) { return millis2Date(date2Millis(date) + timeSpan2Millis(timeSpan, unit)); } /** * Return the milliseconds differ time span by now. * * @param timeSpan The time span. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the milliseconds differ time span by now */ public static long getMillisByNow(final long timeSpan, @TimeConstants.Unit final int unit) { return getMillis(getNowMills(), timeSpan, unit); } /** * Return the formatted time string differ time span by now. *

The pattern is {@code yyyy-MM-dd HH:mm:ss}.

* * @param timeSpan The time span. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the formatted time string differ time span by now */ public static String getStringByNow(final long timeSpan, @TimeConstants.Unit final int unit) { return getStringByNow(timeSpan, getDefaultFormat(), unit); } /** * Return the formatted time string differ time span by now. * * @param timeSpan The time span. * @param format The format. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the formatted time string differ time span by now */ public static String getStringByNow(final long timeSpan, @NonNull final DateFormat format, @TimeConstants.Unit final int unit) { return getString(getNowMills(), format, timeSpan, unit); } /** * Return the date differ time span by now. * * @param timeSpan The time span. * @param unit The unit of time span. *
    *
  • {@link TimeConstants#MSEC}
  • *
  • {@link TimeConstants#SEC }
  • *
  • {@link TimeConstants#MIN }
  • *
  • {@link TimeConstants#HOUR}
  • *
  • {@link TimeConstants#DAY }
  • *
* @return the date differ time span by now */ public static Date getDateByNow(final long timeSpan, @TimeConstants.Unit final int unit) { return getDate(getNowMills(), timeSpan, unit); } /** * Return whether it is today. *

The pattern is {@code yyyy-MM-dd HH:mm:ss}.

* * @param time The formatted time string. * @return {@code true}: yes
{@code false}: no */ public static boolean isToday(final String time) { return isToday(string2Millis(time, getDefaultFormat())); } /** * Return whether it is today. * * @param time The formatted time string. * @param format The format. * @return {@code true}: yes
{@code false}: no */ public static boolean isToday(final String time, @NonNull final DateFormat format) { return isToday(string2Millis(time, format)); } /** * Return whether it is today. * * @param date The date. * @return {@code true}: yes
{@code false}: no */ public static boolean isToday(final Date date) { return isToday(date.getTime()); } /** * Return whether it is today. * * @param millis The milliseconds. * @return {@code true}: yes
{@code false}: no */ public static boolean isToday(final long millis) { long wee = getWeeOfToday(); return millis >= wee && millis < wee + TimeConstants.DAY; } /** * Return whether it is leap year. *

The pattern is {@code yyyy-MM-dd HH:mm:ss}.

* * @param time The formatted time string. * @return {@code true}: yes
{@code false}: no */ public static boolean isLeapYear(final String time) { return isLeapYear(string2Date(time, getDefaultFormat())); } /** * Return whether it is leap year. * * @param time The formatted time string. * @param format The format. * @return {@code true}: yes
{@code false}: no */ public static boolean isLeapYear(final String time, @NonNull final DateFormat format) { return isLeapYear(string2Date(time, format)); } /** * Return whether it is leap year. * * @param date The date. * @return {@code true}: yes
{@code false}: no */ public static boolean isLeapYear(final Date date) { Calendar cal = Calendar.getInstance(); cal.setTime(date); int year = cal.get(Calendar.YEAR); return isLeapYear(year); } /** * Return whether it is leap year. * * @param millis The milliseconds. * @return {@code true}: yes
{@code false}: no */ public static boolean isLeapYear(final long millis) { return isLeapYear(millis2Date(millis)); } /** * Return whether it is leap year. * * @param year The year. * @return {@code true}: yes
{@code false}: no */ public static boolean isLeapYear(final int year) { return year % 4 == 0 && year % 100 != 0 || year % 400 == 0; } /** * Return the day of week in Chinese. *

The 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. *
    *
  • {@link Calendar#ERA}
  • *
  • {@link Calendar#YEAR}
  • *
  • {@link Calendar#MONTH}
  • *
  • ...
  • *
  • {@link Calendar#DST_OFFSET}
  • *
* @return the value of the given calendar field */ public static int getValueByCalendarField(final String time, final int field) { return getValueByCalendarField(string2Date(time, getDefaultFormat()), field); } /** * Returns the value of the given calendar field. * * @param time The formatted time string. * @param format The format. * @param field The given calendar field. *
    *
  • {@link Calendar#ERA}
  • *
  • {@link Calendar#YEAR}
  • *
  • {@link Calendar#MONTH}
  • *
  • ...
  • *
  • {@link Calendar#DST_OFFSET}
  • *
* @return the value of the given calendar field */ public static int getValueByCalendarField(final String time, @NonNull final DateFormat format, final int field) { return getValueByCalendarField(string2Date(time, format), field); } /** * Returns the value of the given calendar field. * * @param date The date. * @param field The given calendar field. *
    *
  • {@link Calendar#ERA}
  • *
  • {@link Calendar#YEAR}
  • *
  • {@link Calendar#MONTH}
  • *
  • ...
  • *
  • {@link Calendar#DST_OFFSET}
  • *
* @return the value of the given calendar field */ public static int getValueByCalendarField(final Date date, final int field) { Calendar cal = Calendar.getInstance(); cal.setTime(date); return cal.get(field); } /** * Returns the value of the given calendar field. * * @param millis The milliseconds. * @param field The given calendar field. *
    *
  • {@link Calendar#ERA}
  • *
  • {@link Calendar#YEAR}
  • *
  • {@link Calendar#MONTH}
  • *
  • ...
  • *
  • {@link Calendar#DST_OFFSET}
  • *
* @return the value of the given calendar field */ public static int getValueByCalendarField(final long millis, final int field) { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(millis); return cal.get(field); } private static final String[] CHINESE_ZODIAC = {"猴", "鸡", "狗", "猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊"}; /** * Return the Chinese zodiac. *

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 * @return field if success, null otherwise. */ private T getField(Class targetClass, Object instance, String fieldName) { try { Field field = targetClass.getDeclaredField(fieldName); field.setAccessible(true); return (T) field.get(instance); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return null; } /** * change the field value * * @param targetClass * @param instance the filed owner * @param fieldName * @param value */ private void setField(Class targetClass, Object instance, String fieldName, Object value) { try { Field field = targetClass.getDeclaredField(fieldName); field.setAccessible(true); field.set(instance, value); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } /** * This method will link the given ViewPager and this BottomNavigationViewEx together so that * changes in one are automatically reflected in the other. This includes scroll state changes * and clicks. * * @param viewPager */ public void setupWithViewPager(@Nullable final ViewPager viewPager) { setupWithViewPager(viewPager, false); } /** * This method will link the given ViewPager and this BottomNavigationViewEx together so that * changes in one are automatically reflected in the other. This includes scroll state changes * and clicks. * * @param viewPager * @param smoothScroll whether ViewPager changed with smooth scroll animation */ public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean smoothScroll) { if (mViewPager != null) { // If we've already been setup with a ViewPager, remove us from it if (mPageChangeListener != null) { mViewPager.removeOnPageChangeListener(mPageChangeListener); } } if (null == viewPager) { mViewPager = null; super.setOnNavigationItemSelectedListener(null); return; } mViewPager = viewPager; // Add our custom OnPageChangeListener to the ViewPager if (mPageChangeListener == null) { mPageChangeListener = new BottomNavigationViewExOnPageChangeListener(this); } viewPager.addOnPageChangeListener(mPageChangeListener); // Now we'll add a navigation item selected listener to set ViewPager's current item OnNavigationItemSelectedListener listener = getOnNavigationItemSelectedListener(); mMyOnNavigationItemSelectedListener = new MyOnNavigationItemSelectedListener(viewPager, this, smoothScroll, listener); super.setOnNavigationItemSelectedListener(mMyOnNavigationItemSelectedListener); } /** * A {@link ViewPager.OnPageChangeListener} class which contains the * necessary calls back to the provided {@link BottomNavigationViewEx} so that the tab position is * kept in sync. *

*

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 mBnveRef; public BottomNavigationViewExOnPageChangeListener(BottomNavigationViewEx bnve) { mBnveRef = new WeakReference<>(bnve); } @Override public void onPageScrollStateChanged(final int state) { } @Override public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) { } @Override public void onPageSelected(final int position) { final BottomNavigationViewEx bnve = mBnveRef.get(); if (null != bnve && !isNavigationItemClicking) bnve.setCurrentItem(position); // Log.d("onPageSelected", "--------- position " + position + " ------------"); } } /** * Decorate OnNavigationItemSelectedListener for setupWithViewPager */ private static class MyOnNavigationItemSelectedListener implements OnNavigationItemSelectedListener { private OnNavigationItemSelectedListener listener; private final WeakReference viewPagerRef; private boolean smoothScroll; private SparseIntArray items;// used for change ViewPager selected item private int previousPosition = -1; MyOnNavigationItemSelectedListener(ViewPager viewPager, BottomNavigationViewEx bnve, boolean smoothScroll, OnNavigationItemSelectedListener listener) { this.viewPagerRef = new WeakReference<>(viewPager); this.listener = listener; this.smoothScroll = smoothScroll; // create items Menu menu = bnve.getMenu(); int size = menu.size(); items = new SparseIntArray(size); for (int i = 0; i < size; i++) { int itemId = menu.getItem(i).getItemId(); items.put(itemId, i); } } public void setOnNavigationItemSelectedListener(OnNavigationItemSelectedListener listener) { this.listener = listener; } @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { int position = items.get(item.getItemId()); // only set item when item changed if (previousPosition == position) { return true; } // Log.d("onNavigationItemSelecte", "position:" + position); // user listener if (null != listener) { boolean bool = listener.onNavigationItemSelected(item); // if the selected is invalid, no need change the view pager if (!bool) return false; } // change view pager ViewPager viewPager = viewPagerRef.get(); if (null == viewPager) return false; // use isNavigationItemClicking flag to avoid `ViewPager.OnPageChangeListener` trigger isNavigationItemClicking = true; viewPager.setCurrentItem(items.get(item.getItemId()), smoothScroll); isNavigationItemClicking = false; // update previous position previousPosition = position; return true; } } @SuppressLint("RestrictedApi") public void enableShiftingMode(int position, boolean enable) { getBottomNavigationItemView(position).setShifting(enable); } @SuppressLint("RestrictedApi") public void setItemBackground(int position, int background) { getBottomNavigationItemView(position).setItemBackground(background); } @SuppressLint("RestrictedApi") public void setIconTintList(int position, ColorStateList tint) { getBottomNavigationItemView(position).setIconTintList(tint); } @SuppressLint("RestrictedApi") public void setTextTintList(int position, ColorStateList tint) { getBottomNavigationItemView(position).setTextColor(tint); } /** * set margin top for all icons * * @param marginTop in px */ public void setIconsMarginTop(int marginTop) { for (int i = 0; i < getItemCount(); i++) { setIconMarginTop(i, marginTop); } } /** * set margin top for icon * * @param position * @param marginTop in px */ @SuppressLint("RestrictedApi") public void setIconMarginTop(int position, int marginTop) { /* 1. BottomNavigationItemView 2. private final int mDefaultMargin; */ BottomNavigationItemView itemView = getBottomNavigationItemView(position); setField(BottomNavigationItemView.class, itemView, "defaultMargin", marginTop); mMenuView.updateMenuView(); } } ================================================ FILE: app/src/main/res/color/selector_color.xml ================================================ ================================================ FILE: app/src/main/res/color/selector_nav_item_color.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_complete.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_setting.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_todo.xml ================================================ ================================================ FILE: app/src/main/res/drawable/selector_button.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_add_todo.xml ================================================