This commit is contained in:
wucongxing 2025-11-18 15:25:59 +08:00
parent 790f719050
commit 69f2eaf336
1227 changed files with 89317 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*/build
.idea/
.gradle/*
*/release
*/debug

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# MyApplication2
备份3.9.5 大字版

1
autosize/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

33
autosize/bintray.gradle Normal file
View File

@ -0,0 +1,33 @@
apply plugin: 'com.novoda.bintray-release'
allprojects {
repositories {
jcenter()
}
tasks.withType(Javadoc) {
options{
encoding "UTF-8"
charSet 'UTF-8'
links "http://docs.oracle.com/javase/7/docs/api"
}
options.addStringOption('Xdoclint:none', '-quiet')
}
}
def siteUrl = 'https://github.com/JessYanCoding/AndroidAutoSize' //
publish {
userOrg = 'jessyancoding' //bintray注册的用户名
groupId = 'me.jessyan' //compile引用时的第1部分groupId
artifactId = 'autosize' //compile引用时的第2部分项目名
publishVersion = rootProject.versionName //compile引用时的第3部分版本号
desc = '一个极低成本的 Android 屏幕适配方案'
website = siteUrl
}

31
autosize/build.gradle Normal file
View File

@ -0,0 +1,31 @@
apply plugin: 'com.android.library'
android {
namespace 'me.jessyan.autosize'
compileSdkVersion 29
defaultConfig {
minSdkVersion 19
targetSdkVersion 29
consumerProguardFiles 'proguard-rules.pro'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
warning 'InvalidPackage'
}
}
dependencies {
implementation 'androidx.annotation:annotation:1.0.0'
implementation 'androidx.fragment:fragment:1.1.0'
}

24
autosize/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,24 @@
# 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
-keep class me.jessyan.autosize.** { *; }
-keep interface me.jessyan.autosize.** { *; }

View File

@ -0,0 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.jessyan.autosize">
<application>
<provider
android:name="me.jessyan.autosize.InitProvider"
android:authorities="${applicationId}.autosize-init-provider"
android:exported="false"
android:multiprocess="true"/>
</application>
</manifest>

View File

@ -0,0 +1,122 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize;
import android.app.Activity;
import android.app.Application;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.FragmentActivity;
import static me.jessyan.autosize.AutoSizeConfig.DEPENDENCY_ANDROIDX;
import static me.jessyan.autosize.AutoSizeConfig.DEPENDENCY_SUPPORT;
/**
* ================================================
* {@link ActivityLifecycleCallbacksImpl} 可用来代替在 BaseActivity 中加入适配代码的传统方式
* {@link ActivityLifecycleCallbacksImpl} 这种方案类似于 AOP, 面向接口, 侵入性低, 方便统一管理, 扩展性强, 并且也支持适配三方库的 {@link Activity}
* <p>
* Created by JessYan on 2018/8/8 14:32
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public class ActivityLifecycleCallbacksImpl implements Application.ActivityLifecycleCallbacks {
/**
* 屏幕适配逻辑策略类
*/
private AutoAdaptStrategy mAutoAdaptStrategy;
/**
* Fragment 支持自定义适配参数
*/
private FragmentLifecycleCallbacksImpl mFragmentLifecycleCallbacks;
private FragmentLifecycleCallbacksImplToAndroidx mFragmentLifecycleCallbacksToAndroidx;
@RequiresApi(api = Build.VERSION_CODES.O)
public ActivityLifecycleCallbacksImpl(AutoAdaptStrategy autoAdaptStrategy) {
if (DEPENDENCY_ANDROIDX) {
mFragmentLifecycleCallbacksToAndroidx = new FragmentLifecycleCallbacksImplToAndroidx(autoAdaptStrategy);
} else if (DEPENDENCY_SUPPORT){
mFragmentLifecycleCallbacks = new FragmentLifecycleCallbacksImpl(autoAdaptStrategy);
}
mAutoAdaptStrategy = autoAdaptStrategy;
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
if (AutoSizeConfig.getInstance().isCustomFragment()) {
if (mFragmentLifecycleCallbacksToAndroidx != null && activity instanceof FragmentActivity) {
((FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(mFragmentLifecycleCallbacksToAndroidx, true);
} else if (mFragmentLifecycleCallbacks != null && activity instanceof FragmentActivity) {
((FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(mFragmentLifecycleCallbacks, true);
}
}
//Activity 中的 setContentView(View) 一定要在 super.onCreate(Bundle); 之后执行
if (mAutoAdaptStrategy != null) {
mAutoAdaptStrategy.applyAdapt(activity, activity);
}
}
@Override
public void onActivityStarted(Activity activity) {
if (mAutoAdaptStrategy != null) {
mAutoAdaptStrategy.applyAdapt(activity, activity);
}
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
/**
* 设置屏幕适配逻辑策略类
*
* @param autoAdaptStrategy {@link AutoAdaptStrategy}
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public void setAutoAdaptStrategy(AutoAdaptStrategy autoAdaptStrategy) {
mAutoAdaptStrategy = autoAdaptStrategy;
if (mFragmentLifecycleCallbacksToAndroidx != null) {
mFragmentLifecycleCallbacksToAndroidx.setAutoAdaptStrategy(autoAdaptStrategy);
} else if (mFragmentLifecycleCallbacks != null) {
mFragmentLifecycleCallbacks.setAutoAdaptStrategy(autoAdaptStrategy);
}
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize;
import android.app.Activity;
import android.app.Application;
import android.util.DisplayMetrics;
/**
* ================================================
* 屏幕适配逻辑策略类, 可通过 {@link AutoSizeConfig#init(Application, boolean, AutoAdaptStrategy)}
* {@link AutoSizeConfig#setAutoAdaptStrategy(AutoAdaptStrategy)} 切换策略
*
* @see DefaultAutoAdaptStrategy
* Created by JessYan on 2018/8/9 15:13
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public interface AutoAdaptStrategy {
/**
* 开始执行屏幕适配逻辑
*
* @param target 需要屏幕适配的对象 (可能是 {@link Activity} 或者 Fragment)
* @param activity 需要拿到当前的 {@link Activity} 才能修改 {@link DisplayMetrics#density}
*/
void applyAdapt(Object target, Activity activity);
}

View File

@ -0,0 +1,385 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize;
import android.app.Activity;
import android.app.Application;
import android.app.Dialog;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.Uri;
import android.util.DisplayMetrics;
import android.util.SparseArray;
import android.view.View;
import java.util.Locale;
import me.jessyan.autosize.external.ExternalAdaptInfo;
import me.jessyan.autosize.external.ExternalAdaptManager;
import me.jessyan.autosize.internal.CancelAdapt;
import me.jessyan.autosize.internal.CustomAdapt;
import me.jessyan.autosize.utils.AutoSizeLog;
import me.jessyan.autosize.utils.Preconditions;
/**
* ================================================
* AndroidAutoSize 用于屏幕适配的核心方法都在这里, 核心原理来自于 <a href="https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA">今日头条官方适配方案</a>
* 此方案只要应用到 {@link Activity} , 这个 {@link Activity} 下的所有 Fragment{@link Dialog}
* 自定义 {@link View} 都会达到适配的效果, 如果某个页面不想使用适配请让该 {@link Activity} 实现 {@link CancelAdapt}
* <p>
* 任何方案都不可能完美, 在成本和收益中做出取舍, 选择出最适合自己的方案即可, 在没有更好的方案出来之前, 只有继续忍耐它的不完美, 或者自己作出改变
* 既然选择, 就不要抱怨, 感谢 今日头条技术团队 张鸿洋 等人对 Android 屏幕适配领域的的贡献
* <p>
* Created by JessYan on 2018/8/8 19:20
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public final class AutoSize {
private static SparseArray<DisplayMetricsInfo> mCache = new SparseArray<>();
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
private static final int MODE_ON_WIDTH = 1 << MODE_SHIFT;
private static final int MODE_DEVICE_SIZE = 2 << MODE_SHIFT;
private AutoSize() {
throw new IllegalStateException("you can't instantiate me!");
}
/**
* 检查 AndroidAutoSize 是否已经初始化
*
* @return {@code false} 表示 AndroidAutoSize 还未初始化, {@code true} 表示 AndroidAutoSize 已经初始化
*/
public static boolean checkInit() {
return AutoSizeConfig.getInstance().getInitDensity() != -1;
}
/**
* 由于 AndroidAutoSize 会通过 {@link InitProvider} 的实例化而自动完成初始化, 并且 {@link AutoSizeConfig#init(Application)}
* 只允许被调用一次, 否则会报错, 所以 {@link AutoSizeConfig#init(Application)} 的调用权限并没有设为 public, 不允许外部使用者调用
* 但由于某些 issues 反应, 可能会在某些特殊情况下出现 {@link InitProvider} 未能正常实例化的情况, 导致 AndroidAutoSize 未能完成初始化
* 所以提供此静态方法用于让外部使用者在异常情况下也可以初始化 AndroidAutoSize, {@link Application#onCreate()} 中调用即可
*
* @param application {@link Application}
*/
public static void checkAndInit(Application application) {
if (!checkInit()) {
AutoSizeConfig.getInstance()
.setLog(true)
.init(application)
.setUseDeviceSize(false);
}
}
/**
* 使用 AndroidAutoSize 初始化时设置的默认适配参数进行适配 (AndroidManifest Meta 属性)
*
* @param activity {@link Activity}
*/
public static void autoConvertDensityOfGlobal(Activity activity) {
if (AutoSizeConfig.getInstance().isBaseOnWidth()) {
autoConvertDensityBaseOnWidth(activity, AutoSizeConfig.getInstance().getDesignWidthInDp());
} else {
autoConvertDensityBaseOnHeight(activity, AutoSizeConfig.getInstance().getDesignHeightInDp());
}
}
/**
* 使用 {@link Activity} Fragment 的自定义参数进行适配
*
* @param activity {@link Activity}
* @param customAdapt {@link Activity} Fragment 需实现 {@link CustomAdapt}
*/
public static void autoConvertDensityOfCustomAdapt(Activity activity, CustomAdapt customAdapt) {
Preconditions.checkNotNull(customAdapt, "customAdapt == null");
float sizeInDp = customAdapt.getSizeInDp();
//如果 CustomAdapt#getSizeInDp() 返回 0, 则使用在 AndroidManifest 上填写的设计图尺寸
if (sizeInDp <= 0) {
if (customAdapt.isBaseOnWidth()) {
sizeInDp = AutoSizeConfig.getInstance().getDesignWidthInDp();
} else {
sizeInDp = AutoSizeConfig.getInstance().getDesignHeightInDp();
}
}
autoConvertDensity(activity, sizeInDp, customAdapt.isBaseOnWidth());
}
/**
* 使用外部三方库的 {@link Activity} Fragment 的自定义适配参数进行适配
*
* @param activity {@link Activity}
* @param externalAdaptInfo 三方库的 {@link Activity} Fragment 提供的适配参数, 需要配合 {@link ExternalAdaptManager#addExternalAdaptInfoOfActivity(Class, ExternalAdaptInfo)}
*/
public static void autoConvertDensityOfExternalAdaptInfo(Activity activity, ExternalAdaptInfo externalAdaptInfo) {
Preconditions.checkNotNull(externalAdaptInfo, "externalAdaptInfo == null");
float sizeInDp = externalAdaptInfo.getSizeInDp();
//如果 ExternalAdaptInfo#getSizeInDp() 返回 0, 则使用在 AndroidManifest 上填写的设计图尺寸
if (sizeInDp <= 0) {
if (externalAdaptInfo.isBaseOnWidth()) {
sizeInDp = AutoSizeConfig.getInstance().getDesignWidthInDp();
} else {
sizeInDp = AutoSizeConfig.getInstance().getDesignHeightInDp();
}
}
autoConvertDensity(activity, sizeInDp, externalAdaptInfo.isBaseOnWidth());
}
/**
* 以宽度为基准进行适配
*
* @param activity {@link Activity}
* @param designWidthInDp 设计图的总宽度
*/
public static void autoConvertDensityBaseOnWidth(Activity activity, float designWidthInDp) {
autoConvertDensity(activity, designWidthInDp, true);
}
/**
* 以高度为基准进行适配
*
* @param activity {@link Activity}
* @param designHeightInDp 设计图的总高度
*/
public static void autoConvertDensityBaseOnHeight(Activity activity, float designHeightInDp) {
autoConvertDensity(activity, designHeightInDp, false);
}
/**
* 这里是今日头条适配方案的核心代码, 核心在于根据当前设备的实际情况做自动计算并转换 {@link DisplayMetrics#density}
* {@link DisplayMetrics#scaledDensity}{@link DisplayMetrics#densityDpi} 这三个值, 额外增加 {@link DisplayMetrics#xdpi}
* 以支持单位 {@code pt}{@code in}{@code mm}
*
* @param activity {@link Activity}
* @param sizeInDp 设计图上的设计尺寸, 单位 dp, 如果 {@param isBaseOnWidth} 设置为 {@code true},
* {@param sizeInDp} 则应该填写设计图的总宽度, 如果 {@param isBaseOnWidth} 设置为 {@code false},
* {@param sizeInDp} 则应该填写设计图的总高度
* @param isBaseOnWidth 是否按照宽度进行等比例适配, {@code true} 为以宽度进行等比例适配, {@code false} 为以高度进行等比例适配
* @see <a href="https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA">今日头条官方适配方案</a>
*/
public static void autoConvertDensity(Activity activity, float sizeInDp, boolean isBaseOnWidth) {
Preconditions.checkNotNull(activity, "activity == null");
Preconditions.checkMainThread();
float subunitsDesignSize = isBaseOnWidth ? AutoSizeConfig.getInstance().getUnitsManager().getDesignWidth()
: AutoSizeConfig.getInstance().getUnitsManager().getDesignHeight();
subunitsDesignSize = subunitsDesignSize > 0 ? subunitsDesignSize : sizeInDp;
int screenSize = isBaseOnWidth ? AutoSizeConfig.getInstance().getScreenWidth()
: AutoSizeConfig.getInstance().getScreenHeight();
int key = Math.round((sizeInDp + subunitsDesignSize + screenSize) * AutoSizeConfig.getInstance().getInitScaledDensity()) & ~MODE_MASK;
key = isBaseOnWidth ? (key | MODE_ON_WIDTH) : (key & ~MODE_ON_WIDTH);
key = AutoSizeConfig.getInstance().isUseDeviceSize() ? (key | MODE_DEVICE_SIZE) : (key & ~MODE_DEVICE_SIZE);
DisplayMetricsInfo displayMetricsInfo = mCache.get(key);
float targetDensity = 0;
int targetDensityDpi = 0;
float targetScaledDensity = 0;
float targetXdpi = 0;
int targetScreenWidthDp;
int targetScreenHeightDp;
if (displayMetricsInfo == null) {
if (isBaseOnWidth) {
targetDensity = AutoSizeConfig.getInstance().getScreenWidth() * 1.0f / sizeInDp;
} else {
targetDensity = AutoSizeConfig.getInstance().getScreenHeight() * 1.0f / sizeInDp;
}
if (AutoSizeConfig.getInstance().getPrivateFontScale() > 0) {
targetScaledDensity = targetDensity * AutoSizeConfig.getInstance().getPrivateFontScale();
} else {
float systemFontScale = AutoSizeConfig.getInstance().isExcludeFontScale() ? 1 : AutoSizeConfig.getInstance().
getInitScaledDensity() * 1.0f / AutoSizeConfig.getInstance().getInitDensity();
targetScaledDensity = targetDensity * systemFontScale;
}
targetDensityDpi = (int) (targetDensity * 160);
targetScreenWidthDp = (int) (AutoSizeConfig.getInstance().getScreenWidth() / targetDensity);
targetScreenHeightDp = (int) (AutoSizeConfig.getInstance().getScreenHeight() / targetDensity);
if (isBaseOnWidth) {
targetXdpi = AutoSizeConfig.getInstance().getScreenWidth() * 1.0f / subunitsDesignSize;
} else {
targetXdpi = AutoSizeConfig.getInstance().getScreenHeight() * 1.0f / subunitsDesignSize;
}
mCache.put(key, new DisplayMetricsInfo(targetDensity, targetDensityDpi, targetScaledDensity, targetXdpi, targetScreenWidthDp, targetScreenHeightDp));
} else {
targetDensity = displayMetricsInfo.getDensity();
targetDensityDpi = displayMetricsInfo.getDensityDpi();
targetScaledDensity = displayMetricsInfo.getScaledDensity();
targetXdpi = displayMetricsInfo.getXdpi();
targetScreenWidthDp = displayMetricsInfo.getScreenWidthDp();
targetScreenHeightDp = displayMetricsInfo.getScreenHeightDp();
}
setDensity(activity, targetDensity, targetDensityDpi, targetScaledDensity, targetXdpi);
setScreenSizeDp(activity, targetScreenWidthDp, targetScreenHeightDp);
AutoSizeLog.d(String.format(Locale.ENGLISH, "The %s has been adapted! \n%s Info: isBaseOnWidth = %s, %s = %f, %s = %f, targetDensity = %f, targetScaledDensity = %f, targetDensityDpi = %d, targetXdpi = %f, targetScreenWidthDp = %d, targetScreenHeightDp = %d"
, activity.getClass().getName(), activity.getClass().getSimpleName(), isBaseOnWidth, isBaseOnWidth ? "designWidthInDp"
: "designHeightInDp", sizeInDp, isBaseOnWidth ? "designWidthInSubunits" : "designHeightInSubunits", subunitsDesignSize
, targetDensity, targetScaledDensity, targetDensityDpi, targetXdpi, targetScreenWidthDp, targetScreenHeightDp));
}
/**
* 取消适配
*
* @param activity {@link Activity}
*/
public static void cancelAdapt(Activity activity) {
Preconditions.checkMainThread();
float initXdpi = AutoSizeConfig.getInstance().getInitXdpi();
switch (AutoSizeConfig.getInstance().getUnitsManager().getSupportSubunits()) {
case PT:
initXdpi = initXdpi / 72f;
break;
case MM:
initXdpi = initXdpi / 25.4f;
break;
default:
}
setDensity(activity, AutoSizeConfig.getInstance().getInitDensity()
, AutoSizeConfig.getInstance().getInitDensityDpi()
, AutoSizeConfig.getInstance().getInitScaledDensity()
, initXdpi);
setScreenSizeDp(activity
, AutoSizeConfig.getInstance().getInitScreenWidthDp()
, AutoSizeConfig.getInstance().getInitScreenHeightDp());
}
/**
* App 中出现多进程并且您需要适配所有的进程就需要在 App 初始化时调用 {@link #initCompatMultiProcess}
* 建议实现自定义 {@link Application} 并在 {@link Application#onCreate()} 中调用 {@link #initCompatMultiProcess}
*
* @param context {@link Context}
*/
public static void initCompatMultiProcess(Context context) {
context.getContentResolver().query(Uri.parse("content://" + context.getPackageName() + ".autosize-init-provider"), null, null, null, null);
}
/**
* 给几大 {@link DisplayMetrics} 赋值
*
* @param activity {@link Activity}
* @param density {@link DisplayMetrics#density}
* @param densityDpi {@link DisplayMetrics#densityDpi}
* @param scaledDensity {@link DisplayMetrics#scaledDensity}
* @param xdpi {@link DisplayMetrics#xdpi}
*/
private static void setDensity(Activity activity, float density, int densityDpi, float scaledDensity, float xdpi) {
DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
setDensity(activityDisplayMetrics, density, densityDpi, scaledDensity, xdpi);
DisplayMetrics appDisplayMetrics = AutoSizeConfig.getInstance().getApplication().getResources().getDisplayMetrics();
setDensity(appDisplayMetrics, density, densityDpi, scaledDensity, xdpi);
//兼容 MIUI
DisplayMetrics activityDisplayMetricsOnMIUI = getMetricsOnMiui(activity.getResources());
DisplayMetrics appDisplayMetricsOnMIUI = getMetricsOnMiui(AutoSizeConfig.getInstance().getApplication().getResources());
if (activityDisplayMetricsOnMIUI != null) {
setDensity(activityDisplayMetricsOnMIUI, density, densityDpi, scaledDensity, xdpi);
}
if (appDisplayMetricsOnMIUI != null) {
setDensity(appDisplayMetricsOnMIUI, density, densityDpi, scaledDensity, xdpi);
}
}
/**
* 赋值
*
* @param displayMetrics {@link DisplayMetrics}
* @param density {@link DisplayMetrics#density}
* @param densityDpi {@link DisplayMetrics#densityDpi}
* @param scaledDensity {@link DisplayMetrics#scaledDensity}
* @param xdpi {@link DisplayMetrics#xdpi}
*/
private static void setDensity(DisplayMetrics displayMetrics, float density, int densityDpi, float scaledDensity, float xdpi) {
if (AutoSizeConfig.getInstance().getUnitsManager().isSupportDP()) {
displayMetrics.density = density;
displayMetrics.densityDpi = densityDpi;
}
if (AutoSizeConfig.getInstance().getUnitsManager().isSupportSP()) {
displayMetrics.scaledDensity = scaledDensity;
}
switch (AutoSizeConfig.getInstance().getUnitsManager().getSupportSubunits()) {
case NONE:
break;
case PT:
displayMetrics.xdpi = xdpi * 72f;
break;
case IN:
displayMetrics.xdpi = xdpi;
break;
case MM:
displayMetrics.xdpi = xdpi * 25.4f;
break;
default:
}
}
/**
* {@link Configuration} 赋值
*
* @param activity {@link Activity}
* @param screenWidthDp {@link Configuration#screenWidthDp}
* @param screenHeightDp {@link Configuration#screenHeightDp}
*/
private static void setScreenSizeDp(Activity activity, int screenWidthDp, int screenHeightDp) {
if (AutoSizeConfig.getInstance().getUnitsManager().isSupportDP() && AutoSizeConfig.getInstance().getUnitsManager().isSupportScreenSizeDP()) {
Configuration activityConfiguration = activity.getResources().getConfiguration();
setScreenSizeDp(activityConfiguration, screenWidthDp, screenHeightDp);
Configuration appConfiguration = AutoSizeConfig.getInstance().getApplication().getResources().getConfiguration();
setScreenSizeDp(appConfiguration, screenWidthDp, screenHeightDp);
}
}
/**
* Configuration赋值
*
* @param configuration {@link Configuration}
* @param screenWidthDp {@link Configuration#screenWidthDp}
* @param screenHeightDp {@link Configuration#screenHeightDp}
*/
private static void setScreenSizeDp(Configuration configuration, int screenWidthDp, int screenHeightDp) {
configuration.screenWidthDp = screenWidthDp;
configuration.screenHeightDp = screenHeightDp;
}
/**
* 解决 MIUI 更改框架导致的 MIUI7 + Android5.1.1 上出现的失效问题 (以及极少数基于这部分 MIUI 去掉 ART 然后置入 XPosed 的手机)
* 来源于: https://github.com/Firedamp/Rudeness/blob/master/rudeness-sdk/src/main/java/com/bulong/rudeness/RudenessScreenHelper.java#L61:5
*
* @param resources {@link Resources}
* @return {@link DisplayMetrics}, 可能为 {@code null}
*/
private static DisplayMetrics getMetricsOnMiui(Resources resources) {
if (AutoSizeConfig.getInstance().isMiui() && AutoSizeConfig.getInstance().getTmpMetricsField() != null) {
try {
return (DisplayMetrics) AutoSizeConfig.getInstance().getTmpMetricsField().get(resources);
} catch (Exception e) {
return null;
}
}
return null;
}
}

View File

@ -0,0 +1,331 @@
/*
* Copyright 2019 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize;
import android.app.Activity;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.DisplayMetrics;
import android.util.SparseArray;
import me.jessyan.autosize.external.ExternalAdaptInfo;
import me.jessyan.autosize.external.ExternalAdaptManager;
import me.jessyan.autosize.internal.CustomAdapt;
import me.jessyan.autosize.utils.Preconditions;
/**
* ================================================
* 当遇到本来适配正常的布局突然出现适配失效适配异常等问题, 重写当前 {@link Activity} {@link Activity#getResources()} 并调用
* {@link AutoSizeCompat} 的对应方法即可解决问题
* <p>
* Created by JessYan on 2018/8/8 19:20
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public final class AutoSizeCompat {
private static SparseArray<DisplayMetricsInfo> mCache = new SparseArray<>();
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
private static final int MODE_ON_WIDTH = 1 << MODE_SHIFT;
private static final int MODE_DEVICE_SIZE = 2 << MODE_SHIFT;
private AutoSizeCompat() {
throw new IllegalStateException("you can't instantiate me!");
}
/**
* 使用 AndroidAutoSize 初始化时设置的默认适配参数进行适配 (AndroidManifest Meta 属性)
*
* @param resources {@link Resources}
*/
public static void autoConvertDensityOfGlobal(Resources resources) {
if (AutoSizeConfig.getInstance().isBaseOnWidth()) {
autoConvertDensityBaseOnWidth(resources, AutoSizeConfig.getInstance().getDesignWidthInDp());
} else {
autoConvertDensityBaseOnHeight(resources, AutoSizeConfig.getInstance().getDesignHeightInDp());
}
}
/**
* 使用 {@link Activity} Fragment 的自定义参数进行适配
*
* @param resources {@link Resources}
* @param customAdapt {@link Activity} Fragment 需实现 {@link CustomAdapt}
*/
public static void autoConvertDensityOfCustomAdapt(Resources resources, CustomAdapt customAdapt) {
Preconditions.checkNotNull(customAdapt, "customAdapt == null");
float sizeInDp = customAdapt.getSizeInDp();
//如果 CustomAdapt#getSizeInDp() 返回 0, 则使用在 AndroidManifest 上填写的设计图尺寸
if (sizeInDp <= 0) {
if (customAdapt.isBaseOnWidth()) {
sizeInDp = AutoSizeConfig.getInstance().getDesignWidthInDp();
} else {
sizeInDp = AutoSizeConfig.getInstance().getDesignHeightInDp();
}
}
autoConvertDensity(resources, sizeInDp, customAdapt.isBaseOnWidth());
}
/**
* 使用外部三方库的 {@link Activity} Fragment 的自定义适配参数进行适配
*
* @param resources {@link Resources}
* @param externalAdaptInfo 三方库的 {@link Activity} Fragment 提供的适配参数, 需要配合 {@link ExternalAdaptManager#addExternalAdaptInfoOfActivity(Class, ExternalAdaptInfo)}
*/
public static void autoConvertDensityOfExternalAdaptInfo(Resources resources, ExternalAdaptInfo externalAdaptInfo) {
Preconditions.checkNotNull(externalAdaptInfo, "externalAdaptInfo == null");
float sizeInDp = externalAdaptInfo.getSizeInDp();
//如果 ExternalAdaptInfo#getSizeInDp() 返回 0, 则使用在 AndroidManifest 上填写的设计图尺寸
if (sizeInDp <= 0) {
if (externalAdaptInfo.isBaseOnWidth()) {
sizeInDp = AutoSizeConfig.getInstance().getDesignWidthInDp();
} else {
sizeInDp = AutoSizeConfig.getInstance().getDesignHeightInDp();
}
}
autoConvertDensity(resources, sizeInDp, externalAdaptInfo.isBaseOnWidth());
}
/**
* 以宽度为基准进行适配
*
* @param resources {@link Resources}
* @param designWidthInDp 设计图的总宽度
*/
public static void autoConvertDensityBaseOnWidth(Resources resources, float designWidthInDp) {
autoConvertDensity(resources, designWidthInDp, true);
}
/**
* 以高度为基准进行适配
*
* @param resources {@link Resources}
* @param designHeightInDp 设计图的总高度
*/
public static void autoConvertDensityBaseOnHeight(Resources resources, float designHeightInDp) {
autoConvertDensity(resources, designHeightInDp, false);
}
/**
* 这里是今日头条适配方案的核心代码, 核心在于根据当前设备的实际情况做自动计算并转换 {@link DisplayMetrics#density}
* {@link DisplayMetrics#scaledDensity}{@link DisplayMetrics#densityDpi} 这三个值, 额外增加 {@link DisplayMetrics#xdpi}
* 以支持单位 {@code pt}{@code in}{@code mm}
*
* @param resources {@link Resources}
* @param sizeInDp 设计图上的设计尺寸, 单位 dp, 如果 {@param isBaseOnWidth} 设置为 {@code true},
* {@param sizeInDp} 则应该填写设计图的总宽度, 如果 {@param isBaseOnWidth} 设置为 {@code false},
* {@param sizeInDp} 则应该填写设计图的总高度
* @param isBaseOnWidth 是否按照宽度进行等比例适配, {@code true} 为以宽度进行等比例适配, {@code false} 为以高度进行等比例适配
* @see <a href="https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA">今日头条官方适配方案</a>
*/
public static void autoConvertDensity(Resources resources, float sizeInDp, boolean isBaseOnWidth) {
Preconditions.checkNotNull(resources, "resources == null");
Preconditions.checkMainThread();
float subunitsDesignSize = isBaseOnWidth ? AutoSizeConfig.getInstance().getUnitsManager().getDesignWidth()
: AutoSizeConfig.getInstance().getUnitsManager().getDesignHeight();
subunitsDesignSize = subunitsDesignSize > 0 ? subunitsDesignSize : sizeInDp;
int screenSize = isBaseOnWidth ? AutoSizeConfig.getInstance().getScreenWidth()
: AutoSizeConfig.getInstance().getScreenHeight();
int key = Math.round((sizeInDp + subunitsDesignSize + screenSize) * AutoSizeConfig.getInstance().getInitScaledDensity()) & ~MODE_MASK;
key = isBaseOnWidth ? (key | MODE_ON_WIDTH) : (key & ~MODE_ON_WIDTH);
key = AutoSizeConfig.getInstance().isUseDeviceSize() ? (key | MODE_DEVICE_SIZE) : (key & ~MODE_DEVICE_SIZE);
DisplayMetricsInfo displayMetricsInfo = mCache.get(key);
float targetDensity = 0;
int targetDensityDpi = 0;
float targetScaledDensity = 0;
float targetXdpi = 0;
int targetScreenWidthDp;
int targetScreenHeightDp;
if (displayMetricsInfo == null) {
if (isBaseOnWidth) {
targetDensity = AutoSizeConfig.getInstance().getScreenWidth() * 1.0f / sizeInDp;
} else {
targetDensity = AutoSizeConfig.getInstance().getScreenHeight() * 1.0f / sizeInDp;
}
if (AutoSizeConfig.getInstance().getPrivateFontScale() > 0) {
targetScaledDensity = targetDensity * AutoSizeConfig.getInstance().getPrivateFontScale();
} else {
float systemFontScale = AutoSizeConfig.getInstance().isExcludeFontScale() ? 1 : AutoSizeConfig.getInstance().
getInitScaledDensity() * 1.0f / AutoSizeConfig.getInstance().getInitDensity();
targetScaledDensity = targetDensity * systemFontScale;
}
targetDensityDpi = (int) (targetDensity * 160);
targetScreenWidthDp = (int) (AutoSizeConfig.getInstance().getScreenWidth() / targetDensity);
targetScreenHeightDp = (int) (AutoSizeConfig.getInstance().getScreenHeight() / targetDensity);
if (isBaseOnWidth) {
targetXdpi = AutoSizeConfig.getInstance().getScreenWidth() * 1.0f / subunitsDesignSize;
} else {
targetXdpi = AutoSizeConfig.getInstance().getScreenHeight() * 1.0f / subunitsDesignSize;
}
mCache.put(key, new DisplayMetricsInfo(targetDensity, targetDensityDpi, targetScaledDensity, targetXdpi, targetScreenWidthDp, targetScreenHeightDp));
} else {
targetDensity = displayMetricsInfo.getDensity();
targetDensityDpi = displayMetricsInfo.getDensityDpi();
targetScaledDensity = displayMetricsInfo.getScaledDensity();
targetXdpi = displayMetricsInfo.getXdpi();
targetScreenWidthDp = displayMetricsInfo.getScreenWidthDp();
targetScreenHeightDp = displayMetricsInfo.getScreenHeightDp();
}
setDensity(resources, targetDensity, targetDensityDpi, targetScaledDensity, targetXdpi);
setScreenSizeDp(resources, targetScreenWidthDp, targetScreenHeightDp);
}
/**
* 取消适配
*
* @param resources {@link Resources}
*/
public static void cancelAdapt(Resources resources) {
Preconditions.checkMainThread();
float initXdpi = AutoSizeConfig.getInstance().getInitXdpi();
switch (AutoSizeConfig.getInstance().getUnitsManager().getSupportSubunits()) {
case PT:
initXdpi = initXdpi / 72f;
break;
case MM:
initXdpi = initXdpi / 25.4f;
break;
default:
}
setDensity(resources, AutoSizeConfig.getInstance().getInitDensity()
, AutoSizeConfig.getInstance().getInitDensityDpi()
, AutoSizeConfig.getInstance().getInitScaledDensity()
, initXdpi);
setScreenSizeDp(resources
, AutoSizeConfig.getInstance().getInitScreenWidthDp()
, AutoSizeConfig.getInstance().getInitScreenHeightDp());
}
/**
* 给几大 {@link DisplayMetrics} 赋值
*
* @param resources {@link Resources}
* @param density {@link DisplayMetrics#density}
* @param densityDpi {@link DisplayMetrics#densityDpi}
* @param scaledDensity {@link DisplayMetrics#scaledDensity}
* @param xdpi {@link DisplayMetrics#xdpi}
*/
private static void setDensity(Resources resources, float density, int densityDpi, float scaledDensity, float xdpi) {
DisplayMetrics activityDisplayMetrics = resources.getDisplayMetrics();
setDensity(activityDisplayMetrics, density, densityDpi, scaledDensity, xdpi);
DisplayMetrics appDisplayMetrics = AutoSizeConfig.getInstance().getApplication().getResources().getDisplayMetrics();
setDensity(appDisplayMetrics, density, densityDpi, scaledDensity, xdpi);
//兼容 MIUI
DisplayMetrics activityDisplayMetricsOnMIUI = getMetricsOnMiui(resources);
DisplayMetrics appDisplayMetricsOnMIUI = getMetricsOnMiui(AutoSizeConfig.getInstance().getApplication().getResources());
if (activityDisplayMetricsOnMIUI != null) {
setDensity(activityDisplayMetricsOnMIUI, density, densityDpi, scaledDensity, xdpi);
}
if (appDisplayMetricsOnMIUI != null) {
setDensity(appDisplayMetricsOnMIUI, density, densityDpi, scaledDensity, xdpi);
}
}
/**
* 赋值
*
* @param displayMetrics {@link DisplayMetrics}
* @param density {@link DisplayMetrics#density}
* @param densityDpi {@link DisplayMetrics#densityDpi}
* @param scaledDensity {@link DisplayMetrics#scaledDensity}
* @param xdpi {@link DisplayMetrics#xdpi}
*/
private static void setDensity(DisplayMetrics displayMetrics, float density, int densityDpi, float scaledDensity, float xdpi) {
if (AutoSizeConfig.getInstance().getUnitsManager().isSupportDP()) {
displayMetrics.density = density;
displayMetrics.densityDpi = densityDpi;
}
if (AutoSizeConfig.getInstance().getUnitsManager().isSupportSP()) {
displayMetrics.scaledDensity = scaledDensity;
}
switch (AutoSizeConfig.getInstance().getUnitsManager().getSupportSubunits()) {
case NONE:
break;
case PT:
displayMetrics.xdpi = xdpi * 72f;
break;
case IN:
displayMetrics.xdpi = xdpi;
break;
case MM:
displayMetrics.xdpi = xdpi * 25.4f;
break;
default:
}
}
/**
* {@link Configuration} 赋值
*
* @param resources {@link Resources}
* @param screenWidthDp {@link Configuration#screenWidthDp}
* @param screenHeightDp {@link Configuration#screenHeightDp}
*/
private static void setScreenSizeDp(Resources resources, int screenWidthDp, int screenHeightDp) {
if (AutoSizeConfig.getInstance().getUnitsManager().isSupportDP() && AutoSizeConfig.getInstance().getUnitsManager().isSupportScreenSizeDP()) {
Configuration activityConfiguration = resources.getConfiguration();
setScreenSizeDp(activityConfiguration, screenWidthDp, screenHeightDp);
Configuration appConfiguration = AutoSizeConfig.getInstance().getApplication().getResources().getConfiguration();
setScreenSizeDp(appConfiguration, screenWidthDp, screenHeightDp);
}
}
/**
* Configuration赋值
*
* @param configuration {@link Configuration}
* @param screenWidthDp {@link Configuration#screenWidthDp}
* @param screenHeightDp {@link Configuration#screenHeightDp}
*/
private static void setScreenSizeDp(Configuration configuration, int screenWidthDp, int screenHeightDp) {
configuration.screenWidthDp = screenWidthDp;
configuration.screenHeightDp = screenHeightDp;
}
/**
* 解决 MIUI 更改框架导致的 MIUI7 + Android5.1.1 上出现的失效问题 (以及极少数基于这部分 MIUI 去掉 ART 然后置入 XPosed 的手机)
* 来源于: https://github.com/Firedamp/Rudeness/blob/master/rudeness-sdk/src/main/java/com/bulong/rudeness/RudenessScreenHelper.java#L61:5
*
* @param resources {@link Resources}
* @return {@link DisplayMetrics}, 可能为 {@code null}
*/
private static DisplayMetrics getMetricsOnMiui(Resources resources) {
if (AutoSizeConfig.getInstance().isMiui() && AutoSizeConfig.getInstance().getTmpMetricsField() != null) {
try {
return (DisplayMetrics) AutoSizeConfig.getInstance().getTmpMetricsField().get(resources);
} catch (Exception e) {
return null;
}
}
return null;
}
}

View File

@ -0,0 +1,714 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize;
import android.app.Activity;
import android.app.Application;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.DisplayMetrics;
import java.lang.reflect.Field;
import me.jessyan.autosize.external.ExternalAdaptManager;
import me.jessyan.autosize.unit.Subunits;
import me.jessyan.autosize.unit.UnitsManager;
import me.jessyan.autosize.utils.AutoSizeLog;
import me.jessyan.autosize.utils.Preconditions;
import me.jessyan.autosize.utils.ScreenUtils;
/**
* ================================================
* AndroidAutoSize 参数配置类, AndroidAutoSize 配置一些必要的自定义参数
* <p>
* Created by JessYan on 2018/8/8 09:58
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public final class AutoSizeConfig {
private static volatile AutoSizeConfig sInstance;
private static final String KEY_DESIGN_WIDTH_IN_DP = "design_width_in_dp";
private static final String KEY_DESIGN_HEIGHT_IN_DP = "design_height_in_dp";
public static final boolean DEPENDENCY_ANDROIDX;
public static final boolean DEPENDENCY_SUPPORT;
private Application mApplication;
/**
* 用来管理外部三方库 {@link Activity} 的适配
*/
private ExternalAdaptManager mExternalAdaptManager = new ExternalAdaptManager();
/**
* 用来管理 AndroidAutoSize 支持的所有单位, AndroidAutoSize 支持五种单位 (dpspptinmm)
*/
private UnitsManager mUnitsManager = new UnitsManager();
/**
* 最初的 {@link DisplayMetrics#density}
*/
private float mInitDensity = -1;
/**
* 最初的 {@link DisplayMetrics#densityDpi}
*/
private int mInitDensityDpi;
/**
* 最初的 {@link DisplayMetrics#scaledDensity}
*/
private float mInitScaledDensity;
/**
* 最初的 {@link DisplayMetrics#xdpi}
*/
private float mInitXdpi;
/**
* 最初的 {@link Configuration#screenWidthDp}
*/
private int mInitScreenWidthDp;
/**
* 最初的 {@link Configuration#screenHeightDp}
*/
private int mInitScreenHeightDp;
/**
* 设计图上的总宽度, 单位 dp
*/
private int mDesignWidthInDp;
/**
* 设计图上的总高度, 单位 dp
*/
private int mDesignHeightInDp;
/**
* 设备的屏幕总宽度, 单位 px
*/
private int mScreenWidth;
/**
* 设备的屏幕总高度, 单位 px, 如果 {@link #isUseDeviceSize} {@code false}, 屏幕总高度会减去状态栏的高度
*/
private int mScreenHeight;
/**
* 状态栏高度, {@link #isUseDeviceSize} {@code false} , AndroidAutoSize 会将 {@link #mScreenHeight} 减去状态栏高度
* AndroidAutoSize 默认使用 {@link ScreenUtils#getStatusBarHeight()} 方法获取状态栏高度
* AndroidAutoSize 使用者可使用 {@link #setStatusBarHeight(int)} 自行设置状态栏高度
*/
private int mStatusBarHeight;
/**
* 为了保证在不同高宽比的屏幕上显示效果也能完全一致, 所以本方案适配时是以设计图宽度与设备实际宽度的比例或设计图高度与设备实际高度的比例应用到
* 每个 View (只能在宽度和高度之中选一个作为基准), 从而使每个 View 的高和宽用同样的比例缩放, 避免在与设计图高宽比不一致的设备上出现适配的 View 高或宽变形的问题
* {@link #isBaseOnWidth} {@code true} 时代表以宽度等比例缩放, {@code false} 代表以高度等比例缩放
* {@link #isBaseOnWidth} 为全局配置, 默认为 {@code true}, 每个 {@link Activity} 也可以单独选择使用高或者宽做等比例缩放
*/
private boolean isBaseOnWidth = true;
/**
* 此字段表示是否使用设备的实际尺寸做适配
* {@link #isUseDeviceSize} {@code true} 表示屏幕高度 {@link #mScreenHeight} 包含状态栏的高度
* {@link #isUseDeviceSize} {@code false} 表示 {@link #mScreenHeight} 会减去状态栏的高度, 默认为 {@code true}
*/
private boolean isUseDeviceSize = true;
/**
* {@link #mActivityLifecycleCallbacks} 可用来代替在 BaseActivity 中加入适配代码的传统方式
* {@link #mActivityLifecycleCallbacks} 这种方案类似于 AOP, 面向接口, 侵入性低, 方便统一管理, 扩展性强, 并且也支持适配三方库的 {@link Activity}
*/
private ActivityLifecycleCallbacksImpl mActivityLifecycleCallbacks;
/**
* 框架具有 热插拔 特性, 支持在项目运行中动态停止和重新启动适配功能
*
* @see #stop(Activity)
* @see #restart()
*/
private boolean isStop;
/**
* 是否让框架支持自定义 Fragment 的适配参数, 由于这个需求是比较少见的, 所以须要使用者手动开启
*/
private boolean isCustomFragment;
/**
* 屏幕方向, {@code true} 为纵向, {@code false} 为横向
*/
private boolean isVertical;
/**
* 是否屏蔽系统字体大小对 AndroidAutoSize 的影响, 如果为 {@code true}, App 内的字体的大小将不会跟随系统设置中字体大小的改变
* 如果为 {@code false}, 则会跟随系统设置中字体大小的改变, 默认为 {@code false}
*/
private boolean isExcludeFontScale;
/**
* 区别于系统字体大小的放大比例, AndroidAutoSize 允许 APP 内部可以独立于系统字体大小之外独自拥有全局调节 APP 字体大小的能力
* 当然, APP 内您必须使用 sp 来作为字体的单位, 否则此功能无效, 将此值设为 0 则取消此功能
*/
private float privateFontScale;
/**
* 是否是 Miui 系统
*/
private boolean isMiui;
/**
* Miui 系统中的 mTmpMetrics 字段
*/
private Field mTmpMetricsField;
/**
* 屏幕适配监听器用于监听屏幕适配时的一些事件
*/
private onAdaptListener mOnAdaptListener;
static {
DEPENDENCY_ANDROIDX = findClassByClassName("androidx.fragment.app.FragmentActivity");
DEPENDENCY_SUPPORT = findClassByClassName("android.support.v4.app.FragmentActivity");
}
private static boolean findClassByClassName(String className) {
boolean hasDependency;
try {
Class.forName(className);
hasDependency = true;
} catch (ClassNotFoundException e) {
hasDependency = false;
}
return hasDependency;
}
public static AutoSizeConfig getInstance() {
if (sInstance == null) {
synchronized (AutoSizeConfig.class) {
if (sInstance == null) {
sInstance = new AutoSizeConfig();
}
}
}
return sInstance;
}
private AutoSizeConfig() {
}
public Application getApplication() {
Preconditions.checkNotNull(mApplication, "Please call the AutoSizeConfig#init() first");
return mApplication;
}
/**
* v0.7.0 以后, 框架会在 APP 启动时自动调用此方法进行初始化, 使用者无需手动初始化, 初始化方法只能调用一次, 否则报错
* 此方法默认使用以宽度进行等比例适配, 如想使用以高度进行等比例适配, 请调用 {@link #init(Application, boolean)}
*
* @param application {@link Application}
*/
AutoSizeConfig init(Application application) {
return init(application, true, null);
}
/**
* v0.7.0 以后, 框架会在 APP 启动时自动调用此方法进行初始化, 使用者无需手动初始化, 初始化方法只能调用一次, 否则报错
* 此方法使用默认的 {@link AutoAdaptStrategy} 策略, 如想使用自定义的 {@link AutoAdaptStrategy} 策略
* 请调用 {@link #init(Application, boolean, AutoAdaptStrategy)}
*
* @param application {@link Application}
* @param isBaseOnWidth 详情请查看 {@link #isBaseOnWidth} 的注释
*/
AutoSizeConfig init(Application application, boolean isBaseOnWidth) {
return init(application, isBaseOnWidth, null);
}
/**
* v0.7.0 以后, 框架会在 APP 启动时自动调用此方法进行初始化, 使用者无需手动初始化, 初始化方法只能调用一次, 否则报错
*
* @param application {@link Application}
* @param isBaseOnWidth 详情请查看 {@link #isBaseOnWidth} 的注释
* @param strategy {@link AutoAdaptStrategy}, {@code null} 则使用 {@link DefaultAutoAdaptStrategy}
*/
AutoSizeConfig init(final Application application, boolean isBaseOnWidth, AutoAdaptStrategy strategy) {
Preconditions.checkArgument(mInitDensity == -1, "AutoSizeConfig#init() can only be called once");
Preconditions.checkNotNull(application, "application == null");
this.mApplication = application;
this.isBaseOnWidth = isBaseOnWidth;
final DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
final Configuration configuration = Resources.getSystem().getConfiguration();
//设置一个默认值, 避免在低配设备上因为获取 MetaData 过慢, 导致适配时未能正常获取到设计图尺寸
//建议使用者在低配设备上主动在 Application#onCreate 中调用 setDesignWidthInDp 替代以使用 AndroidManifest 配置设计图尺寸的方式
if (AutoSizeConfig.getInstance().getUnitsManager().getSupportSubunits() == Subunits.NONE) {
mDesignWidthInDp = 360;
mDesignHeightInDp = 640;
} else {
mDesignWidthInDp = 1080;
mDesignHeightInDp = 1920;
}
getMetaData(application);
isVertical = application.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
int[] screenSize = ScreenUtils.getScreenSize(application);
mScreenWidth = screenSize[0];
mScreenHeight = screenSize[1];
mStatusBarHeight = ScreenUtils.getStatusBarHeight();
AutoSizeLog.d("designWidthInDp = " + mDesignWidthInDp + ", designHeightInDp = " + mDesignHeightInDp + ", screenWidth = " + mScreenWidth + ", screenHeight = " + mScreenHeight);
mInitDensity = displayMetrics.density;
mInitDensityDpi = displayMetrics.densityDpi;
mInitScaledDensity = displayMetrics.scaledDensity;
mInitXdpi = displayMetrics.xdpi;
mInitScreenWidthDp = configuration.screenWidthDp;
mInitScreenHeightDp = configuration.screenHeightDp;
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig != null) {
if (newConfig.fontScale > 0) {
mInitScaledDensity =
Resources.getSystem().getDisplayMetrics().scaledDensity;
AutoSizeLog.d("initScaledDensity = " + mInitScaledDensity + " on ConfigurationChanged");
}
isVertical = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT;
int[] screenSize = ScreenUtils.getScreenSize(application);
mScreenWidth = screenSize[0];
mScreenHeight = screenSize[1];
}
}
@Override
public void onLowMemory() {
}
});
AutoSizeLog.d("initDensity = " + mInitDensity + ", initScaledDensity = " + mInitScaledDensity);
mActivityLifecycleCallbacks = new ActivityLifecycleCallbacksImpl(new WrapperAutoAdaptStrategy(strategy == null ? new DefaultAutoAdaptStrategy() : strategy));
application.registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
if ("MiuiResources".equals(application.getResources().getClass().getSimpleName()) || "XResources".equals(application.getResources().getClass().getSimpleName())) {
isMiui = true;
try {
mTmpMetricsField = Resources.class.getDeclaredField("mTmpMetrics");
mTmpMetricsField.setAccessible(true);
} catch (Exception e) {
mTmpMetricsField = null;
}
}
return this;
}
/**
* 重新开始框架的运行
* 框架具有 热插拔 特性, 支持在项目运行中动态停止和重新启动适配功能
*/
public void restart() {
Preconditions.checkNotNull(mActivityLifecycleCallbacks, "Please call the AutoSizeConfig#init() first");
synchronized (AutoSizeConfig.class) {
if (isStop) {
mApplication.registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
isStop = false;
}
}
}
/**
* 停止框架的运行
* 框架具有 热插拔 特性, 支持在项目运行中动态停止和重新启动适配功能
*/
public void stop(Activity activity) {
Preconditions.checkNotNull(mActivityLifecycleCallbacks, "Please call the AutoSizeConfig#init() first");
synchronized (AutoSizeConfig.class) {
if (!isStop) {
mApplication.unregisterActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
AutoSize.cancelAdapt(activity);
isStop = true;
}
}
}
/**
* 设置屏幕适配逻辑策略类
*
* @param autoAdaptStrategy {@link AutoAdaptStrategy}
*/
public AutoSizeConfig setAutoAdaptStrategy(AutoAdaptStrategy autoAdaptStrategy) {
Preconditions.checkNotNull(autoAdaptStrategy, "autoAdaptStrategy == null");
Preconditions.checkNotNull(mActivityLifecycleCallbacks, "Please call the AutoSizeConfig#init() first");
mActivityLifecycleCallbacks.setAutoAdaptStrategy(new WrapperAutoAdaptStrategy(autoAdaptStrategy));
return this;
}
/**
* 设置屏幕适配监听器
*
* @param onAdaptListener {@link onAdaptListener}
*/
public AutoSizeConfig setOnAdaptListener(onAdaptListener onAdaptListener) {
Preconditions.checkNotNull(onAdaptListener, "onAdaptListener == null");
mOnAdaptListener = onAdaptListener;
return this;
}
/**
* 是否全局按照宽度进行等比例适配
*
* @param baseOnWidth {@code true} 为按照宽度, {@code false} 为按照高度
* @see #isBaseOnWidth 详情请查看这个字段的注释
*/
public AutoSizeConfig setBaseOnWidth(boolean baseOnWidth) {
isBaseOnWidth = baseOnWidth;
return this;
}
/**
* 是否使用设备的实际尺寸做适配
*
* @param useDeviceSize {@code true} 为使用设备的实际尺寸 (包含状态栏), {@code false} 为不使用设备的实际尺寸 (不包含状态栏)
* @see #isUseDeviceSize 详情请查看这个字段的注释
*/
public AutoSizeConfig setUseDeviceSize(boolean useDeviceSize) {
isUseDeviceSize = useDeviceSize;
return this;
}
/**
* 是否打印 Log
*
* @param log {@code true} 为打印
*/
public AutoSizeConfig setLog(boolean log) {
AutoSizeLog.setDebug(log);
return this;
}
/**
* 是否让框架支持自定义 Fragment 的适配参数, 由于这个需求是比较少见的, 所以须要使用者手动开启
*
* @param customFragment {@code true} 为支持
*/
public AutoSizeConfig setCustomFragment(boolean customFragment) {
isCustomFragment = customFragment;
return this;
}
/**
* 框架是否已经开启支持自定义 Fragment 的适配参数
*
* @return {@code true} 为支持
*/
public boolean isCustomFragment() {
return isCustomFragment;
}
/**
* 框架是否已经停止运行
*
* @return {@code false} 框架正在运行, {@code true} 框架已经停止运行
*/
public boolean isStop() {
return isStop;
}
/**
* {@link ExternalAdaptManager} 用来管理外部三方库 {@link Activity} 的适配
*
* @return {@link #mExternalAdaptManager}
*/
public ExternalAdaptManager getExternalAdaptManager() {
return mExternalAdaptManager;
}
/**
* {@link UnitsManager} 用来管理 AndroidAutoSize 支持的所有单位, AndroidAutoSize 支持五种单位 (dpspptinmm)
*
* @return {@link #mUnitsManager}
*/
public UnitsManager getUnitsManager() {
return mUnitsManager;
}
/**
* 返回 {@link #mOnAdaptListener}
*
* @return {@link #mOnAdaptListener}
*/
public onAdaptListener getOnAdaptListener() {
return mOnAdaptListener;
}
/**
* 返回 {@link #isBaseOnWidth}
*
* @return {@link #isBaseOnWidth}
*/
public boolean isBaseOnWidth() {
return isBaseOnWidth;
}
/**
* 返回 {@link #isUseDeviceSize}
*
* @return {@link #isUseDeviceSize}
*/
public boolean isUseDeviceSize() {
return isUseDeviceSize;
}
/**
* 返回 {@link #mScreenWidth}
*
* @return {@link #mScreenWidth}
*/
public int getScreenWidth() {
return mScreenWidth;
}
/**
* 返回 {@link #mScreenHeight}
*
* @return {@link #mScreenHeight}
*/
public int getScreenHeight() {
return isUseDeviceSize() ? mScreenHeight : mScreenHeight - mStatusBarHeight;
}
/**
* 获取 {@link #mDesignWidthInDp}
*
* @return {@link #mDesignWidthInDp}
*/
public int getDesignWidthInDp() {
Preconditions.checkArgument(mDesignWidthInDp > 0, "you must set " + KEY_DESIGN_WIDTH_IN_DP + " in your AndroidManifest file");
return mDesignWidthInDp;
}
/**
* 获取 {@link #mDesignHeightInDp}
*
* @return {@link #mDesignHeightInDp}
*/
public int getDesignHeightInDp() {
Preconditions.checkArgument(mDesignHeightInDp > 0, "you must set " + KEY_DESIGN_HEIGHT_IN_DP + " in your AndroidManifest file");
return mDesignHeightInDp;
}
/**
* 获取 {@link #mInitDensity}
*
* @return {@link #mInitDensity}
*/
public float getInitDensity() {
return mInitDensity;
}
/**
* 获取 {@link #mInitDensityDpi}
*
* @return {@link #mInitDensityDpi}
*/
public int getInitDensityDpi() {
return mInitDensityDpi;
}
/**
* 获取 {@link #mInitScaledDensity}
*
* @return {@link #mInitScaledDensity}
*/
public float getInitScaledDensity() {
return mInitScaledDensity;
}
/**
* 获取 {@link #mInitXdpi}
*
* @return {@link #mInitXdpi}
*/
public float getInitXdpi() {
return mInitXdpi;
}
/**
* 获取 {@link #mInitScreenWidthDp}
*
* @return {@link #mInitScreenWidthDp}
*/
public int getInitScreenWidthDp() {
return mInitScreenWidthDp;
}
/**
* 获取 {@link #mInitScreenHeightDp}
*
* @return {@link #mInitScreenHeightDp}
*/
public int getInitScreenHeightDp() {
return mInitScreenHeightDp;
}
/**
* 获取屏幕方向
*
* @return {@code true} 为纵向, {@code false} 为横向
*/
public boolean isVertical() {
return isVertical;
}
/**
* 返回 {@link #isMiui}
*
* @return {@link #isMiui}
*/
public boolean isMiui() {
return isMiui;
}
/**
* 返回 {@link #mTmpMetricsField}
*
* @return {@link #mTmpMetricsField}
*/
public Field getTmpMetricsField() {
return mTmpMetricsField;
}
/**
* 设置屏幕方向
*
* @param vertical {@code true} 为纵向, {@code false} 为横向
*/
public AutoSizeConfig setVertical(boolean vertical) {
isVertical = vertical;
return this;
}
/**
* 是否屏蔽系统字体大小对 AndroidAutoSize 的影响, 如果为 {@code true}, App 内的字体的大小将不会跟随系统设置中字体大小的改变
* 如果为 {@code false}, 则会跟随系统设置中字体大小的改变, 默认为 {@code false}
*
* @return {@link #isExcludeFontScale}
*/
public boolean isExcludeFontScale() {
return isExcludeFontScale;
}
/**
* 是否屏蔽系统字体大小对 AndroidAutoSize 的影响, 如果为 {@code true}, App 内的字体的大小将不会跟随系统设置中字体大小的改变
* 如果为 {@code false}, 则会跟随系统设置中字体大小的改变, 默认为 {@code false}
*
* @param excludeFontScale 是否屏蔽
*/
public AutoSizeConfig setExcludeFontScale(boolean excludeFontScale) {
isExcludeFontScale = excludeFontScale;
return this;
}
/**
* 区别于系统字体大小的放大比例, AndroidAutoSize 允许 APP 内部可以独立于系统字体大小之外独自拥有全局调节 APP 字体大小的能力
* 当然, APP 内您必须使用 sp 来作为字体的单位, 否则此功能无效
*
* @param fontScale 字体大小放大的比例, 设为 0 则取消此功能
*/
public AutoSizeConfig setPrivateFontScale(float fontScale) {
privateFontScale = fontScale;
return this;
}
/**
* 区别于系统字体大小的放大比例, AndroidAutoSize 允许 APP 内部可以独立于系统字体大小之外独自拥有全局调节 APP 字体大小的能力
* 当然, APP 内您必须使用 sp 来作为字体的单位, 否则此功能无效
*
* @return 私有的字体大小放大比例
*/
public float getPrivateFontScale() {
return privateFontScale;
}
/**
* 设置屏幕宽度
*
* @param screenWidth 屏幕宽度
*/
public AutoSizeConfig setScreenWidth(int screenWidth) {
Preconditions.checkArgument(screenWidth > 0, "screenWidth must be > 0");
mScreenWidth = screenWidth;
return this;
}
/**
* 设置屏幕高度
*
* @param screenHeight 屏幕高度 (需要包含状态栏)
*/
public AutoSizeConfig setScreenHeight(int screenHeight) {
Preconditions.checkArgument(screenHeight > 0, "screenHeight must be > 0");
mScreenHeight = screenHeight;
return this;
}
/**
* 设置全局设计图宽度
*
* @param designWidthInDp 设计图宽度
*/
public AutoSizeConfig setDesignWidthInDp(int designWidthInDp) {
Preconditions.checkArgument(designWidthInDp > 0, "designWidthInDp must be > 0");
mDesignWidthInDp = designWidthInDp;
return this;
}
/**
* 设置全局设计图高度
*
* @param designHeightInDp 设计图高度
*/
public AutoSizeConfig setDesignHeightInDp(int designHeightInDp) {
Preconditions.checkArgument(designHeightInDp > 0, "designHeightInDp must be > 0");
mDesignHeightInDp = designHeightInDp;
return this;
}
/**
* 设置状态栏高度
*
* @param statusBarHeight 状态栏高度
*/
public AutoSizeConfig setStatusBarHeight(int statusBarHeight) {
Preconditions.checkArgument(statusBarHeight > 0, "statusBarHeight must be > 0");
mStatusBarHeight = statusBarHeight;
return this;
}
/**
* 获取使用者在 AndroidManifest 中填写的 Meta 信息
* <p>
* Example usage:
* <pre>
* <meta-data android:name="design_width_in_dp"
* android:value="360"/>
* <meta-data android:name="design_height_in_dp"
* android:value="640"/>
* </pre>
*
* @param context {@link Context}
*/
private void getMetaData(final Context context) {
new Thread(new Runnable() {
@Override
public void run() {
PackageManager packageManager = context.getPackageManager();
ApplicationInfo applicationInfo;
try {
applicationInfo = packageManager.getApplicationInfo(context
.getPackageName(), PackageManager.GET_META_DATA);
if (applicationInfo != null && applicationInfo.metaData != null) {
if (applicationInfo.metaData.containsKey(KEY_DESIGN_WIDTH_IN_DP)) {
mDesignWidthInDp = (int) applicationInfo.metaData.get(KEY_DESIGN_WIDTH_IN_DP);
}
if (applicationInfo.metaData.containsKey(KEY_DESIGN_HEIGHT_IN_DP)) {
mDesignHeightInDp = (int) applicationInfo.metaData.get(KEY_DESIGN_HEIGHT_IN_DP);
}
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}).start();
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize;
import android.app.Activity;
import android.app.Application;
import java.util.Locale;
import me.jessyan.autosize.external.ExternalAdaptInfo;
import me.jessyan.autosize.internal.CancelAdapt;
import me.jessyan.autosize.internal.CustomAdapt;
import me.jessyan.autosize.utils.AutoSizeLog;
/**
* ================================================
* 屏幕适配逻辑策略默认实现类, 可通过 {@link AutoSizeConfig#init(Application, boolean, AutoAdaptStrategy)}
* {@link AutoSizeConfig#setAutoAdaptStrategy(AutoAdaptStrategy)} 切换策略
*
* @see AutoAdaptStrategy
* Created by JessYan on 2018/8/9 15:57
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public class DefaultAutoAdaptStrategy implements AutoAdaptStrategy {
@Override
public void applyAdapt(Object target, Activity activity) {
//检查是否开启了外部三方库的适配模式, 只要不主动调用 ExternalAdaptManager 的方法, 下面的代码就不会执行
if (AutoSizeConfig.getInstance().getExternalAdaptManager().isRun()) {
if (AutoSizeConfig.getInstance().getExternalAdaptManager().isCancelAdapt(target.getClass())) {
AutoSizeLog.w(String.format(Locale.ENGLISH, "%s canceled the adaptation!", target.getClass().getName()));
AutoSize.cancelAdapt(activity);
return;
} else {
ExternalAdaptInfo info = AutoSizeConfig.getInstance().getExternalAdaptManager()
.getExternalAdaptInfoOfActivity(target.getClass());
if (info != null) {
AutoSizeLog.d(String.format(Locale.ENGLISH, "%s used %s for adaptation!", target.getClass().getName(), ExternalAdaptInfo.class.getName()));
AutoSize.autoConvertDensityOfExternalAdaptInfo(activity, info);
return;
}
}
}
//如果 target 实现 CancelAdapt 接口表示放弃适配, 所有的适配效果都将失效
if (target instanceof CancelAdapt) {
AutoSizeLog.w(String.format(Locale.ENGLISH, "%s canceled the adaptation!", target.getClass().getName()));
AutoSize.cancelAdapt(activity);
return;
}
//如果 target 实现 CustomAdapt 接口表示该 target 想自定义一些用于适配的参数, 从而改变最终的适配效果
if (target instanceof CustomAdapt) {
AutoSizeLog.d(String.format(Locale.ENGLISH, "%s implemented by %s!", target.getClass().getName(), CustomAdapt.class.getName()));
AutoSize.autoConvertDensityOfCustomAdapt(activity, (CustomAdapt) target);
} else {
AutoSizeLog.d(String.format(Locale.ENGLISH, "%s used the global configuration.", target.getClass().getName()));
AutoSize.autoConvertDensityOfGlobal(activity);
}
}
}

View File

@ -0,0 +1,150 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.DisplayMetrics;
/**
* ================================================
* {@link DisplayMetrics} 封装类
* <p>
* Created by JessYan on 2018/8/11 16:42
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public class DisplayMetricsInfo implements Parcelable {
private float density;
private int densityDpi;
private float scaledDensity;
private float xdpi;
private int screenWidthDp;
private int screenHeightDp;
public DisplayMetricsInfo(float density, int densityDpi, float scaledDensity, float xdpi) {
this.density = density;
this.densityDpi = densityDpi;
this.scaledDensity = scaledDensity;
this.xdpi = xdpi;
}
public DisplayMetricsInfo(float density, int densityDpi, float scaledDensity, float xdpi, int screenWidthDp, int screenHeightDp) {
this.density = density;
this.densityDpi = densityDpi;
this.scaledDensity = scaledDensity;
this.xdpi = xdpi;
this.screenWidthDp = screenWidthDp;
this.screenHeightDp = screenHeightDp;
}
public float getDensity() {
return density;
}
public void setDensity(float density) {
this.density = density;
}
public int getDensityDpi() {
return densityDpi;
}
public void setDensityDpi(int densityDpi) {
this.densityDpi = densityDpi;
}
public float getScaledDensity() {
return scaledDensity;
}
public void setScaledDensity(float scaledDensity) {
this.scaledDensity = scaledDensity;
}
public float getXdpi() {
return xdpi;
}
public void setXdpi(float xdpi) {
this.xdpi = xdpi;
}
public int getScreenWidthDp() {
return screenWidthDp;
}
public void setScreenWidthDp(int screenWidthDp) {
this.screenWidthDp = screenWidthDp;
}
public int getScreenHeightDp() {
return screenHeightDp;
}
public void setScreenHeightDp(int screenHeightDp) {
this.screenHeightDp = screenHeightDp;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(this.density);
dest.writeInt(this.densityDpi);
dest.writeFloat(this.scaledDensity);
dest.writeFloat(this.xdpi);
dest.writeInt(this.screenWidthDp);
dest.writeInt(this.screenHeightDp);
}
protected DisplayMetricsInfo(Parcel in) {
this.density = in.readFloat();
this.densityDpi = in.readInt();
this.scaledDensity = in.readFloat();
this.xdpi = in.readFloat();
this.screenWidthDp = in.readInt();
this.screenHeightDp = in.readInt();
}
public static final Creator<DisplayMetricsInfo> CREATOR = new Creator<DisplayMetricsInfo>() {
@Override
public DisplayMetricsInfo createFromParcel(Parcel source) {
return new DisplayMetricsInfo(source);
}
@Override
public DisplayMetricsInfo[] newArray(int size) {
return new DisplayMetricsInfo[size];
}
};
@Override
public String toString() {
return "DisplayMetricsInfo{" +
"density=" + density +
", densityDpi=" + densityDpi +
", scaledDensity=" + scaledDensity +
", xdpi=" + xdpi +
", screenWidthDp=" + screenWidthDp +
", screenHeightDp=" + screenHeightDp +
'}';
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
/**
* ================================================
* {@link FragmentLifecycleCallbacksImpl} 可用来代替在 BaseFragment 中加入适配代码的传统方式
* {@link FragmentLifecycleCallbacksImpl} 这种方案类似于 AOP, 面向接口, 侵入性低, 方便统一管理, 扩展性强, 并且也支持适配三方库的 {@link Fragment}
* <p>
* Created by JessYan on 2018/8/25 13:52
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public class FragmentLifecycleCallbacksImpl extends FragmentManager.FragmentLifecycleCallbacks {
/**
* 屏幕适配逻辑策略类
*/
private AutoAdaptStrategy mAutoAdaptStrategy;
public FragmentLifecycleCallbacksImpl(AutoAdaptStrategy autoAdaptStrategy) {
mAutoAdaptStrategy = autoAdaptStrategy;
}
@Override
public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {
if (mAutoAdaptStrategy != null) {
mAutoAdaptStrategy.applyAdapt(f, f.getActivity());
}
}
/**
* 设置屏幕适配逻辑策略类
*
* @param autoAdaptStrategy {@link AutoAdaptStrategy}
*/
public void setAutoAdaptStrategy(AutoAdaptStrategy autoAdaptStrategy) {
mAutoAdaptStrategy = autoAdaptStrategy;
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
/**
* ================================================
* {@link FragmentLifecycleCallbacksImplToAndroidx} 可用来代替在 BaseFragment 中加入适配代码的传统方式
* {@link FragmentLifecycleCallbacksImplToAndroidx} 这种方案类似于 AOP, 面向接口, 侵入性低, 方便统一管理, 扩展性强, 并且也支持适配三方库的 {@link Fragment}
* <p>
* Created by JessYan on 2018/8/25 13:52
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public class FragmentLifecycleCallbacksImplToAndroidx extends FragmentManager.FragmentLifecycleCallbacks {
/**
* 屏幕适配逻辑策略类
*/
private AutoAdaptStrategy mAutoAdaptStrategy;
public FragmentLifecycleCallbacksImplToAndroidx(AutoAdaptStrategy autoAdaptStrategy) {
mAutoAdaptStrategy = autoAdaptStrategy;
}
@Override
public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {
if (mAutoAdaptStrategy != null) {
mAutoAdaptStrategy.applyAdapt(f, f.getActivity());
}
}
/**
* 设置屏幕适配逻辑策略类
*
* @param autoAdaptStrategy {@link AutoAdaptStrategy}
*/
public void setAutoAdaptStrategy(AutoAdaptStrategy autoAdaptStrategy) {
mAutoAdaptStrategy = autoAdaptStrategy;
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize;
import android.content.Context;
import android.app.Application;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import me.jessyan.autosize.utils.AutoSizeUtils;
/**
* ================================================
* 通过声明 {@link ContentProvider} 自动完成初始化
* Created by JessYan on 2018/8/19 11:55
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public class InitProvider extends ContentProvider {
@Override
public boolean onCreate() {
Context application = getContext().getApplicationContext();
if (application == null) {
application = AutoSizeUtils.getApplicationByReflect();
}
AutoSizeConfig.getInstance()
.setLog(true)
.init((Application) application)
.setUseDeviceSize(false);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize;
import android.app.Activity;
/**
* ================================================
* {@link AutoAdaptStrategy} 的包装者, 用于给 {@link AutoAdaptStrategy} 的实现类增加一些额外的职责
* <p>
* Created by JessYan on 2018/10/30 15:07
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public class WrapperAutoAdaptStrategy implements AutoAdaptStrategy {
private final AutoAdaptStrategy mAutoAdaptStrategy;
public WrapperAutoAdaptStrategy(AutoAdaptStrategy autoAdaptStrategy) {
mAutoAdaptStrategy = autoAdaptStrategy;
}
@Override
public void applyAdapt(Object target, Activity activity) {
onAdaptListener onAdaptListener = AutoSizeConfig.getInstance().getOnAdaptListener();
if (onAdaptListener != null){
onAdaptListener.onAdaptBefore(target, activity);
}
if (mAutoAdaptStrategy != null) {
mAutoAdaptStrategy.applyAdapt(target, activity);
}
if (onAdaptListener != null){
onAdaptListener.onAdaptAfter(target, activity);
}
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize.external;
import android.app.Activity;
import android.os.Parcel;
import android.os.Parcelable;
/**
* ================================================
* {@link ExternalAdaptInfo} 用来存储外部三方库的适配参数, 因为 AndroidAutoSize 默认会对项目中的所有模块都进行适配
* 三方库的 {@link Activity} 也不例外, 但三方库的适配参数可能和自己项目中的适配参数不一致, 导致三方库的适配效果和理想的效果差别很大
* 所以需要向 AndroidAutoSize 提供三方库的适配参数, 已完成对三方库的屏幕适配
* <p>
* Created by JessYan on 2018/8/9 18:19
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public class ExternalAdaptInfo implements Parcelable {
/**
* 是否按照宽度进行等比例适配 (为了保证在高宽比不同的屏幕上也能正常适配, 所以只能在宽度和高度之中选一个作为基准进行适配)
* {@code true} 为按照宽度适配, {@code false} 为按照高度适配
*/
private boolean isBaseOnWidth;
/**
* 设计图上的设计尺寸, 单位 dp (三方库页面的设计图尺寸可能无法获知, 所以如果想让三方库的适配效果达到最好, 只有靠不断的尝试)
* {@link #sizeInDp} 须配合 {@link #isBaseOnWidth} 使用, 规则如下:
* 如果 {@link #isBaseOnWidth} 设置为 {@code true}, {@link #sizeInDp} 则应该设置为设计图的总宽度
* 如果 {@link #isBaseOnWidth} 设置为 {@code false}, {@link #sizeInDp} 则应该设置为设计图的总高度
* 如果您不需要自定义设计图上的设计尺寸, 想继续使用在 AndroidManifest 中填写的设计图尺寸, {@link #sizeInDp} 则设置为 {@code 0}
*/
private float sizeInDp;
public ExternalAdaptInfo(boolean isBaseOnWidth) {
this.isBaseOnWidth = isBaseOnWidth;
}
public ExternalAdaptInfo(boolean isBaseOnWidth, float sizeInDp) {
this.isBaseOnWidth = isBaseOnWidth;
this.sizeInDp = sizeInDp;
}
public boolean isBaseOnWidth() {
return isBaseOnWidth;
}
public void setBaseOnWidth(boolean baseOnWidth) {
isBaseOnWidth = baseOnWidth;
}
public float getSizeInDp() {
return sizeInDp;
}
public void setSizeInDp(float sizeInDp) {
this.sizeInDp = sizeInDp;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte(this.isBaseOnWidth ? (byte) 1 : (byte) 0);
dest.writeFloat(this.sizeInDp);
}
protected ExternalAdaptInfo(Parcel in) {
this.isBaseOnWidth = in.readByte() != 0;
this.sizeInDp = in.readFloat();
}
public static final Creator<ExternalAdaptInfo> CREATOR = new Creator<ExternalAdaptInfo>() {
@Override
public ExternalAdaptInfo createFromParcel(Parcel source) {
return new ExternalAdaptInfo(source);
}
@Override
public ExternalAdaptInfo[] newArray(int size) {
return new ExternalAdaptInfo[size];
}
};
@Override
public String toString() {
return "ExternalAdaptInfo{" +
"isBaseOnWidth=" + isBaseOnWidth +
", sizeInDp=" + sizeInDp +
'}';
}
}

View File

@ -0,0 +1,141 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize.external;
import android.app.Activity;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import me.jessyan.autosize.AutoSizeConfig;
import me.jessyan.autosize.utils.Preconditions;
/**
* ================================================
* 管理三方库的适配信息和状态, 通过 {@link AutoSizeConfig#getExternalAdaptManager()} 获取, 切勿自己 new
* AndroidAutoSize 通过实现接口的方式来让每个 {@link Activity} 都具有自定义适配参数的功能, 从而让每个 {@link Activity} 都可以自定义适配效果
* 但通过远程依赖的三方库并不能修改源码, 所以也不能让三方库的 {@link Activity} 实现接口, 实现接口的方式就显得无能为力
* {@link ExternalAdaptManager} 就是专门用来处理这个问题, 项目初始化时把对应的三方库 {@link Activity} 传入 {@link ExternalAdaptManager} 即可
* <p>
* Created by JessYan on 2018/8/10 14:40
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public class ExternalAdaptManager {
private List<String> mCancelAdaptList;
private Map<String, ExternalAdaptInfo> mExternalAdaptInfos;
private boolean isRun;
/**
* 将不需要适配的第三方库 {@link Activity} 添加进来 (但不局限于三方库), 即可让该 {@link Activity} 的适配效果失效
* <p>
* 支持链式调用, :
* {@link ExternalAdaptManager#addCancelAdaptOfActivity(Class)#addCancelAdaptOfActivity(Class)}
*
* @param targetClass {@link Activity} class, Fragment class
*/
public synchronized ExternalAdaptManager addCancelAdaptOfActivity(Class<?> targetClass) {
Preconditions.checkNotNull(targetClass, "targetClass == null");
if (!isRun) {
isRun = true;
}
if (mCancelAdaptList == null) {
mCancelAdaptList = new ArrayList<>();
}
mCancelAdaptList.add(targetClass.getCanonicalName());
return this;
}
/**
* 将需要提供自定义适配参数的三方库 {@link Activity} 添加进来 (但不局限于三方库), 即可让该 {@link Activity} 根据自己提供的适配参数进行适配
* 默认的全局适配参数不能满足您时可以使用此方法
* <p>
* 一般用于三方库的 Activity, 因为三方库的设计图尺寸可能和项目自身的设计图尺寸不一致, 所以要想完美适配三方库的页面
* 就需要提供三方库的设计图尺寸, 以及适配的方向 (以宽为基准还是高为基准?)
* 三方库页面的设计图尺寸可能无法获知, 所以如果想让三方库的适配效果达到最好, 只有靠不断的尝试
* 由于 AndroidAutoSize 可以让布局在所有设备上都等比例缩放, 所以只要您在一个设备上测试出了一个最完美的设计图尺寸
* 那这个三方库页面在其他设备上也会呈现出同样的适配效果, 等比例缩放, 所以也就完成了三方库页面的屏幕适配
* 即使在不改三方库源码的情况下也可以完美适配三方库的页面, 这就是 AndroidAutoSize 的优势
* 但前提是三方库页面的布局使用的是 dp sp, 如果布局全部使用的 px, AndroidAutoSize 也将无能为力
* <p>
* 支持链式调用, :
* {@link ExternalAdaptManager#addExternalAdaptInfoOfActivity(Class, ExternalAdaptInfo)#addExternalAdaptInfoOfActivity(Class, ExternalAdaptInfo)}
*
* @param targetClass {@link Activity} class, Fragment class
* @param info {@link ExternalAdaptInfo} 适配参数
*/
public synchronized ExternalAdaptManager addExternalAdaptInfoOfActivity(Class<?> targetClass, ExternalAdaptInfo info) {
Preconditions.checkNotNull(targetClass, "targetClass == null");
if (!isRun) {
isRun = true;
}
if (mExternalAdaptInfos == null) {
mExternalAdaptInfos = new HashMap<>(16);
}
mExternalAdaptInfos.put(targetClass.getCanonicalName(), info);
return this;
}
/**
* 这个 {@link Activity} 是否存在在取消适配的列表中, 如果在, 则该 {@link Activity} 适配失效
*
* @param targetClass {@link Activity} class, Fragment class
* @return {@code true} 为存在, {@code false} 为不存在
*/
public synchronized boolean isCancelAdapt(Class<?> targetClass) {
Preconditions.checkNotNull(targetClass, "targetClass == null");
if (mCancelAdaptList == null) {
return false;
}
return mCancelAdaptList.contains(targetClass.getCanonicalName());
}
/**
* 这个 {@link Activity} 是否提供有自定义的适配参数, 如果有则使用此适配参数进行适配
*
* @param targetClass {@link Activity} class, Fragment class
* @return 如果返回 {@code null} 则说明该 {@link Activity} 没有提供自定义的适配参数
*/
public synchronized ExternalAdaptInfo getExternalAdaptInfoOfActivity(Class<?> targetClass) {
Preconditions.checkNotNull(targetClass, "targetClass == null");
if (mExternalAdaptInfos == null) {
return null;
}
return mExternalAdaptInfos.get(targetClass.getCanonicalName());
}
/**
* 此管理器是否已经启动
*
* @return {@code true} 为已经启动, {@code false} 为没有启动
*/
public boolean isRun() {
return isRun;
}
/**
* 设置管理器的运行状态
*
* @param run {@code true} 为让管理器启动运行, {@code false} 为让管理器停止运行
*/
public ExternalAdaptManager setRun(boolean run) {
isRun = run;
return this;
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize.internal;
import android.app.Activity;
/**
* ================================================
* AndroidAutoSize 默认项目中的所有模块都使用适配功能, 三方库的 {@link Activity} 也不例外
* 如果某个页面不想使用适配功能, 请让该页面 {@link Activity} 实现此接口
* 实现此接口表示放弃适配, 所有的适配效果都将失效
* <p>
* Created by JessYan on 2018/8/9 09:54
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public interface CancelAdapt {
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize.internal;
import android.app.Activity;
/**
* ================================================
* 如果某些页面不想使用 AndroidAutoSize 初始化时设置的默认适配参数, 请让该页面 {@link Activity} 实现此接口
* 实现此接口即可自定义用于适配的一些参数, 从而影响最终的适配效果
* <p>
* Created by JessYan on 2018/8/9 10:25
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public interface CustomAdapt {
/**
* 是否按照宽度进行等比例适配 (为了保证在高宽比不同的屏幕上也能正常适配, 所以只能在宽度和高度之中选一个作为基准进行适配)
*
* @return {@code true} 为按照宽度适配, {@code false} 为按照高度适配
*/
boolean isBaseOnWidth();
/**
* 返回设计图上的设计尺寸, 单位 dp
* {@link #getSizeInDp} 须配合 {@link #isBaseOnWidth()} 使用, 规则如下:
* 如果 {@link #isBaseOnWidth()} 返回 {@code true}, {@link #getSizeInDp} 则应该返回设计图的总宽度
* 如果 {@link #isBaseOnWidth()} 返回 {@code false}, {@link #getSizeInDp} 则应该返回设计图的总高度
* 如果您不需要自定义设计图上的设计尺寸, 想继续使用在 AndroidManifest 中填写的设计图尺寸, {@link #getSizeInDp} 则返回 {@code 0}
*
* @return 设计图上的设计尺寸, 单位 dp
*/
float getSizeInDp();
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize;
import android.app.Activity;
/**
* ================================================
* 屏幕适配监听器用于监听屏幕适配时的一些事件
* <p>
* Created by JessYan on 2018/10/30 16:29
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public interface onAdaptListener {
/**
* 在屏幕适配前调用
*
* @param target 需要屏幕适配的对象 (可能是 {@link Activity} 或者 Fragment)
* @param activity 当前 {@link Activity}
*/
void onAdaptBefore(Object target, Activity activity);
/**
* 在屏幕适配后调用
*
* @param target 需要屏幕适配的对象 (可能是 {@link Activity} 或者 Fragment)
* @param activity 当前 {@link Activity}
*/
void onAdaptAfter(Object target, Activity activity);
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize.unit;
import android.util.DisplayMetrics;
/**
* ================================================
* AndroidAutoSize 支持一些在 Android 系统上比较少见的单位作为副单位, 用于规避修改 {@link DisplayMetrics#density}
* 所造成的对于其他使用 dp 布局的系统控件或三方库控件的不良影响
* <p>
* Created by JessYan on 2018/8/28 10:27
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public enum Subunits {
/**
* 不使用副单位
*/
NONE,
/**
* 单位 pt
*
* @see android.util.TypedValue#COMPLEX_UNIT_PT
*/
PT,
/**
* 单位 in
*
* @see android.util.TypedValue#COMPLEX_UNIT_IN
*/
IN,
/**
* 单位 mm
*
* @see android.util.TypedValue#COMPLEX_UNIT_MM
*/
MM
}

View File

@ -0,0 +1,213 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize.unit;
import android.util.DisplayMetrics;
import me.jessyan.autosize.utils.Preconditions;
/**
* ================================================
* 管理 AndroidAutoSize 支持的所有单位, AndroidAutoSize 支持五种单位 (dpspptinmm)
* 其中 dpsp 这两个是比较常见的单位, 作为 AndroidAutoSize 的主单位, 默认被 AndroidAutoSize 支持
* ptinmm 这三个是比较少见的单位, 只可以选择其中的一个, 作为 AndroidAutoSize 的副单位, dpsp 一起被 AndroidAutoSize 支持
* 副单位是用于规避修改 {@link DisplayMetrics#density} 所造成的对于其他使用 dp 布局的系统控件或三方库控件的不良影响
* 您选择什么单位, 就在 layout 文件中用什么单位布局
* <p>
* 两个主单位和一个副单位, 可以随时使用下面的方法关闭和重新开启对它们的支持
* 如果您想完全规避修改 {@link DisplayMetrics#density} 所造成的对于其他使用 dp 布局的系统控件或三方库控件的不良影响
* 那请调用 {@link #setSupportDP}{@link #setSupportSP} 都设置为 {@code false}, 停止对两个主单位的支持 (如果开启 sp, 对其他三方库控件影响不大, 也可以不关闭对 sp 的支持)
* 并调用 {@link #setSupportSubunits} 从三个冷门单位中选择一个作为副单位 (三个单位的效果都是一样的, 按自己的喜好选择, 比如我就喜欢 mm, 翻译为中文是妹妹的意思)
* 然后在 layout 文件中只使用这个副单位进行布局, 这样就可以完全规避修改 {@link DisplayMetrics#density} 所造成的问题
* 因为 dpsp 这两个单位在其他系统控件或三方库控件中都非常常见, 但三个冷门单位却非常少见
* <p>
* Created by JessYan on 2018/8/28 10:21
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public class UnitsManager {
/**
* 设计图上的总宽度, 建议单位为 px, 当使用者想将旧项目从主单位过渡到副单位, 或从副单位过渡到主单位时使用
* 因为在使用主单位时, 建议在 AndroidManifest 中填写设计图的 dp 尺寸, 比如 360 * 640
* 而副单位有一个特性是可以直接在 AndroidManifest 中填写设计图的 px 尺寸, 比如 1080 * 1920
* 但在 AndroidManifest 中却只能填写一套设计图尺寸, 并且已经填写了主单位的设计图尺寸
* 所以当项目中同时存在副单位和主单位, 并且副单位的设计图尺寸与主单位的设计图尺寸不同时, 就需要在 {@link UnitsManager} 中保存副单位的设计图尺寸
*/
private float mDesignWidth;
/**
* 设计图上的总高度, 建议单位为 px, 当使用者想将旧项目从主单位过渡到副单位, 或从副单位过渡到主单位时使用
* 因为在使用主单位时, 建议在 AndroidManifest 中填写设计图的 dp 尺寸, 比如 360 * 640
* 而副单位有一个特性是可以直接在 AndroidManifest 中填写设计图的 px 尺寸, 比如 1080 * 1920
* 但在 AndroidManifest 中却只能填写一套设计图尺寸, 并且已经填写了主单位的设计图尺寸
* 所以当项目中同时存在副单位和主单位, 并且副单位的设计图尺寸与主单位的设计图尺寸不同时, 就需要在 {@link UnitsManager} 中保存副单位的设计图尺寸
*/
private float mDesignHeight;
/**
* 是否支持 dp 单位, 默认支持
*/
private boolean isSupportDP = true;
/**
* 是否支持 sp 单位, 默认支持
*/
private boolean isSupportSP = true;
/**
* 是否支持副单位, 以什么为副单位? 默认不支持
*/
private Subunits mSupportSubunits = Subunits.NONE;
/**
* 是否支持 ScreenSizeDp 修改, 默认不支持
*/
private boolean isSupportScreenSizeDP = false;
/**
* 设置设计图尺寸
*
* @param designWidth 设计图上的总宽度, 建议单位为 px
* @param designHeight 设计图上的总高度, 建议单位为 px
* @return {@link UnitsManager}
* @see #mDesignWidth 详情请查看这个字段的注释
* @see #mDesignHeight 详情请查看这个字段的注释
*/
public UnitsManager setDesignSize(float designWidth, float designHeight) {
setDesignWidth(designWidth);
setDesignHeight(designHeight);
return this;
}
/**
* 返回 {@link #mDesignWidth}
*
* @return {@link #mDesignWidth}
*/
public float getDesignWidth() {
return mDesignWidth;
}
/**
* 设置设计图上的总宽度, 建议单位为 px
*
* @param designWidth 设计图上的总宽度, 建议单位为 px
* @return {@link UnitsManager}
* @see #mDesignWidth 详情请查看这个字段的注释
*/
public UnitsManager setDesignWidth(float designWidth) {
Preconditions.checkArgument(designWidth > 0, "designWidth must be > 0");
mDesignWidth = designWidth;
return this;
}
/**
* 返回 {@link #mDesignHeight}
*
* @return {@link #mDesignHeight}
*/
public float getDesignHeight() {
return mDesignHeight;
}
/**
* 设置设计图上的总高度, 建议单位为 px
*
* @param designHeight 设计图上的总高度, 建议单位为 px
* @return {@link UnitsManager}
* @see #mDesignHeight 详情请查看这个字段的注释
*/
public UnitsManager setDesignHeight(float designHeight) {
Preconditions.checkArgument(designHeight > 0, "designHeight must be > 0");
mDesignHeight = designHeight;
return this;
}
/**
* 是否支持 dp 单位, 默认支持, 详情请看类文件的注释 {@link UnitsManager}
*
* @return {@code true} 为支持, {@code false} 为不支持
*/
public boolean isSupportDP() {
return isSupportDP;
}
/**
* 是否让 AndroidAutoSize 支持 dp 单位, 默认支持, 详情请看类文件的注释 {@link UnitsManager}
*
* @param supportDP {@code true} 为支持, {@code false} 为不支持
*/
public UnitsManager setSupportDP(boolean supportDP) {
isSupportDP = supportDP;
return this;
}
/**
* 是否支持 sp 单位, 默认支持, 详情请看类文件的注释 {@link UnitsManager}
*
* @return {@code true} 为支持, {@code false} 为不支持
*/
public boolean isSupportSP() {
return isSupportSP;
}
/**
* 是否让 AndroidAutoSize 支持 sp 单位, 默认支持, 详情请看类文件的注释 {@link UnitsManager}
*
* @param supportSP {@code true} 为支持, {@code false} 为不支持
*/
public UnitsManager setSupportSP(boolean supportSP) {
isSupportSP = supportSP;
return this;
}
/**
* AndroidAutoSize 以什么单位为副单位, 默认为 {@link Subunits#NONE}, 即不支持副单位, 详情请看类文件的注释 {@link UnitsManager}
*
* @return {@link Subunits}
*/
public Subunits getSupportSubunits() {
return mSupportSubunits;
}
/**
* 是否支持 ScreenSizeDp 修改, 默认不支持, 详情请看类文件的注释 {@link UnitsManager}
*
* @return {@code true} 为支持, {@code false} 为不支持
*/
public boolean isSupportScreenSizeDP() {
return isSupportScreenSizeDP;
}
/**
* 是否让 AndroidAutoSize 支持 ScreenSizeDp 修改, 默认不支持, 详情请看类文件的注释 {@link UnitsManager}
*
* @param supportScreenSizeDP {@code true} 为支持, {@code false} 为不支持
*/
public UnitsManager setSupportScreenSizeDP(boolean supportScreenSizeDP) {
isSupportScreenSizeDP = supportScreenSizeDP;
return this;
}
/**
* AndroidAutoSize 以什么单位为副单位, ptinmm 这三个冷门单位中选择一个即可, 三个效果都是一样的
* 按自己的喜好选择, 比如我就喜欢 mm, 翻译为中文是妹妹的意思
* 默认为 {@link Subunits#NONE}, 即不支持副单位, 详情请看类文件的注释 {@link UnitsManager}
*
* @param supportSubunits {@link Subunits}
*/
public UnitsManager setSupportSubunits(Subunits supportSubunits) {
mSupportSubunits = Preconditions.checkNotNull(supportSubunits,
"The supportSubunits can not be null, use Subunits.NONE instead");
return this;
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize.utils;
import android.util.Log;
/**
* ================================================
* Created by JessYan on 2018/8/8 18:48
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public class AutoSizeLog {
private static final String TAG = "AndroidAutoSize";
private static boolean debug;
private AutoSizeLog() {
throw new IllegalStateException("you can't instantiate me!");
}
public static boolean isDebug() {
return debug;
}
public static void setDebug(boolean debug) {
AutoSizeLog.debug = debug;
}
public static void d(String message) {
if (debug) {
Log.d(TAG, message);
}
}
public static void w(String message) {
if (debug) {
Log.w(TAG, message);
}
}
public static void e(String message) {
if (debug) {
Log.e(TAG, message);
}
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize.utils;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.TypedValue;
import android.app.Application;
import java.lang.reflect.InvocationTargetException;
/**
* ================================================
* AndroidAutoSize 常用工具类
* <p>
* Created by JessYan on 2018/8/25 15:24
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public class AutoSizeUtils {
private AutoSizeUtils() {
throw new IllegalStateException("you can't instantiate me!");
}
public static int dp2px(Context context, float value) {
return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, context.getResources().getDisplayMetrics()) + 0.5f);
}
public static int sp2px(Context context, float value) {
return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, value, context.getResources().getDisplayMetrics()) + 0.5f);
}
public static int pt2px(Context context, float value) {
return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, value, context.getResources().getDisplayMetrics()) + 0.5f);
}
public static int in2px(Context context, float value) {
return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_IN, value, context.getResources().getDisplayMetrics()) + 0.5f);
}
public static int mm2px(Context context, float value) {
return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, value, context.getResources().getDisplayMetrics()) + 0.5f);
}
public static Application getApplicationByReflect() {
try {
@SuppressLint("PrivateApi")
Class<?> activityThread = Class.forName("android.app.ActivityThread");
Object thread = activityThread.getMethod("currentActivityThread").invoke(null);
Object app = activityThread.getMethod("getApplication").invoke(thread);
if (app == null) {
throw new NullPointerException("you should init first");
}
return (Application) app;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
throw new NullPointerException("you should init first");
}
}

View File

@ -0,0 +1,191 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize.utils;
import android.os.Looper;
/**
* ================================================
* Created by JessYan on 26/09/2016 13:59
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public final class Preconditions {
private Preconditions() {
throw new IllegalStateException("you can't instantiate me!");
}
public static void checkArgument(boolean expression) {
if (!expression) {
throw new IllegalArgumentException();
}
}
public static void checkArgument(boolean expression, Object errorMessage) {
if (!expression) {
throw new IllegalArgumentException(String.valueOf(errorMessage));
}
}
public static void checkArgument(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) {
if (!expression) {
throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs));
}
}
public static void checkState(boolean expression) {
if (!expression) {
throw new IllegalStateException();
}
}
public static void checkState(boolean expression, Object errorMessage) {
if (!expression) {
throw new IllegalStateException(String.valueOf(errorMessage));
}
}
public static void checkState(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) {
if (!expression) {
throw new IllegalStateException(format(errorMessageTemplate, errorMessageArgs));
}
}
public static <T> T checkNotNull(T reference) {
if (reference == null) {
throw new NullPointerException();
} else {
return reference;
}
}
public static <T> T checkNotNull(T reference, Object errorMessage) {
if (reference == null) {
throw new NullPointerException(String.valueOf(errorMessage));
} else {
return reference;
}
}
public static <T> T checkNotNull(T reference, String errorMessageTemplate, Object... errorMessageArgs) {
if (reference == null) {
throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs));
} else {
return reference;
}
}
public static int checkElementIndex(int index, int size) {
return checkElementIndex(index, size, "index");
}
public static int checkElementIndex(int index, int size, String desc) {
if (index >= 0 && index < size) {
return index;
} else {
throw new IndexOutOfBoundsException(badElementIndex(index, size, desc));
}
}
/**
* Throws {@link IllegalStateException} if the calling thread is not the application's main
* thread.
*
* @throws IllegalStateException If the calling thread is not the application's main thread.
*/
public static void checkMainThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("Not in applications main thread");
}
}
private static String badElementIndex(int index, int size, String desc) {
if (index < 0) {
return format("%s (%s) must not be negative", new Object[]{desc, Integer.valueOf(index)});
} else if (size < 0) {
throw new IllegalArgumentException((new StringBuilder(26)).append("negative size: ").append(size).toString());
} else {
return format("%s (%s) must be less than size (%s)", new Object[]{desc, Integer.valueOf(index), Integer.valueOf(size)});
}
}
public static int checkPositionIndex(int index, int size) {
return checkPositionIndex(index, size, "index");
}
public static int checkPositionIndex(int index, int size, String desc) {
if (index >= 0 && index <= size) {
return index;
} else {
throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc));
}
}
private static String badPositionIndex(int index, int size, String desc) {
if (index < 0) {
return format("%s (%s) must not be negative", new Object[]{desc, Integer.valueOf(index)});
} else if (size < 0) {
throw new IllegalArgumentException((new StringBuilder(26)).append("negative size: ").append(size).toString());
} else {
return format("%s (%s) must not be greater than size (%s)", new Object[]{desc, Integer.valueOf(index), Integer.valueOf(size)});
}
}
public static void checkPositionIndexes(int start, int end, int size) {
if (start < 0 || end < start || end > size) {
throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size));
}
}
private static String badPositionIndexes(int start, int end, int size) {
return start >= 0 && start <= size ? (end >= 0 && end <= size ? format("end index (%s) must not be less than start index (%s)", new Object[]{Integer.valueOf(end), Integer.valueOf(start)}) : badPositionIndex(end, size, "end index")) : badPositionIndex(start, size, "start index");
}
static String format(String template, Object... args) {
template = String.valueOf(template);
StringBuilder builder = new StringBuilder(template.length() + 16 * args.length);
int templateStart = 0;
int i;
int placeholderStart;
for (i = 0; i < args.length; templateStart = placeholderStart + 2) {
placeholderStart = template.indexOf("%s", templateStart);
if (placeholderStart == -1) {
break;
}
builder.append(template.substring(templateStart, placeholderStart));
builder.append(args[i++]);
}
builder.append(template.substring(templateStart));
if (i < args.length) {
builder.append(" [");
builder.append(args[i++]);
while (i < args.length) {
builder.append(", ");
builder.append(args[i++]);
}
builder.append(']');
}
return builder.toString();
}
}

View File

@ -0,0 +1,122 @@
/*
* Copyright 2018 JessYan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jessyan.autosize.utils;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.os.Build;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.WindowManager;
/**
* ================================================
* Created by JessYan on 26/09/2016 16:59
* <a href="mailto:jess.yan.effort@gmail.com">Contact me</a>
* <a href="https://github.com/JessYanCoding">Follow me</a>
* ================================================
*/
public class ScreenUtils {
private ScreenUtils() {
throw new IllegalStateException("you can't instantiate me!");
}
public static int getStatusBarHeight() {
int result = 0;
try {
int resourceId = Resources.getSystem().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = Resources.getSystem().getDimensionPixelSize(resourceId);
}
} catch (Resources.NotFoundException e) {
e.printStackTrace();
}
return result;
}
/**
* 获取当前的屏幕尺寸
*
* @param context {@link Context}
* @return 屏幕尺寸
*/
public static int[] getScreenSize(Context context) {
int[] size = new int[2];
WindowManager w = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display d = w.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
d.getMetrics(metrics);
size[0] = metrics.widthPixels;
size[1] = metrics.heightPixels;
return size;
}
/**
* 获取原始的屏幕尺寸
*
* @param context {@link Context}
* @return 屏幕尺寸
*/
public static int[] getRawScreenSize(Context context) {
int[] size = new int[2];
WindowManager w = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display d = w.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
d.getMetrics(metrics);
// since SDK_INT = 1;
int widthPixels = metrics.widthPixels;
int heightPixels = metrics.heightPixels;
// includes window decorations (statusbar bar/menu bar)
if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT < 17)
try {
widthPixels = (Integer) Display.class.getMethod("getRawWidth").invoke(d);
heightPixels = (Integer) Display.class.getMethod("getRawHeight").invoke(d);
} catch (Exception ignored) {
}
// includes window decorations (statusbar bar/menu bar)
if (Build.VERSION.SDK_INT >= 17)
try {
Point realSize = new Point();
Display.class.getMethod("getRealSize", Point.class).invoke(d, realSize);
widthPixels = realSize.x;
heightPixels = realSize.y;
} catch (Exception ignored) {
}
size[0] = widthPixels;
size[1] = heightPixels;
return size;
}
public static int getHeightOfNavigationBar(Context context) {
//如果小米手机开启了全面屏手势隐藏了导航栏则返回 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (Settings.Global.getInt(context.getContentResolver(), "force_fsg_nav_bar", 0) != 0) {
return 0;
}
}
int realHeight = getRawScreenSize(context)[1];
int displayHeight = getScreenSize(context)[1];
return realHeight - displayHeight;
}
}

97
build.gradle Normal file
View File

@ -0,0 +1,97 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
maven {
url 'https://maven.aliyun.com/repository/central'
}
mavenLocal()
mavenCentral()
google()
// jcenter()
maven {
url 'https://maven.aliyun.com/repository/google/'
}
maven {
url 'https://maven.aliyun.com/repository/jcenter/'
}
maven { url 'https://jitpack.io' }
maven { url 'https://developer.huawei.com/repo/' }
maven { url 'https://repo1.maven.org/maven2/' }
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
}
dependencies {
classpath 'com.android.tools.build:gradle:8.7.3'
classpath 'com.huawei.agconnect:agcp:1.4.1.300'
// Kotlin Gradle - AGP 8.7.3 Kotlin 1.9.0+
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
maven {
url 'https://maven.aliyun.com/repository/central'
}
mavenLocal()
mavenCentral()
// jcenter()
maven {
url 'https://maven.aliyun.com/repository/google/'
}
maven {
url 'https://maven.aliyun.com/repository/jcenter/'
}
maven { url "https://maven.aliyun.com/nexus/content/groups/public/" }
maven { url 'https://maven.aliyun.com/nexus/content/repositories/releases/' }
maven { url 'https://developer.huawei.com/repo/' }
maven { url 'https://jitpack.io' }
// maven {
// url "https://dl.bintray.com/pingxx/maven"
// }
maven { url 'https://repo1.maven.org/maven2/' }
//
// maven { url "https://dl.bintray.com/thelasterstar/maven/" }
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
//
maven {
credentials {
username '609cc5623a10edbf36da9615'
password 'EbkbzTNHRJ=P'
}
url 'https://packages.aliyun.com/maven/repository/2102846-release-8EVsoM/'
}
}
// Kotlin
// Kotlin 1.8.0 kotlin-stdlib-jdk7 kotlin-stdlib-jdk8 kotlin-stdlib
configurations.all {
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
// Kotlin 1.9.22 AGP 8.7.3
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'org.jetbrains.kotlin' && details.requested.name == 'kotlin-stdlib') {
details.useVersion '1.9.22'
}
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

File diff suppressed because one or more lines are too long

18
gradle.properties Normal file
View File

@ -0,0 +1,18 @@
## For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
#Tue Nov 11 14:27:53 CST 2025
android.disableResourceReflation=false
android.enableJetifier=true
android.nonFinalResIds=false
android.useAndroidX=true
org.gradle.jvmargs=-Xmx4096M -Dkotlin.daemon.jvm.options\="-Xmx4096M"

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,9 @@
#Mon Nov 03 18:12:25 CST 2025
distributionBase=GRADLE_USER_HOME
#distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
#distributionUrl=https://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
#distributionUrl=https\://services.gradle.org/distributions/gradle-8.7.3-bin.zip

160
gradlew vendored Normal file
View File

@ -0,0 +1,160 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
gradlew.bat vendored Normal file
View File

@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

8
local.properties Normal file
View File

@ -0,0 +1,8 @@
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Fri Nov 07 09:03:28 CST 2025
sdk.dir=C\:\\Users\\wucongxing\\AppData\\Local\\Android\\Sdk

1
pager-bottom-tab-strip/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,29 @@
apply plugin: 'com.android.library'
android {
namespace 'me.majiajie.pagerbottomtabstrip'
compileSdkVersion 29
defaultConfig {
minSdkVersion 19
targetSdkVersion 29
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
}
dependencies {
implementation 'androidx.annotation:annotation:1.2.0'
implementation 'androidx.viewpager:viewpager:1.0.0'
}
//apply from: 'upload_to_maven_central.gradle'

View File

@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Users\MJJ\Develop\SDK/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# 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 *;
#}

View File

@ -0,0 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.majiajie.pagerbottomtabstrip">
<application android:label="@string/app_name" />
</manifest>

View File

@ -0,0 +1,25 @@
package me.majiajie.pagerbottomtabstrip;
import androidx.viewpager.widget.ViewPager;
interface BottomLayoutController {
/**
* 方便适配ViewPager页面切换<p>
* 注意ViewPager页面数量必须等于导航栏的Item数量
*
* @param viewPager {@link ViewPager}
*/
void setupWithViewPager(ViewPager viewPager);
/**
* 向下移动隐藏导航栏
*/
void hideBottomLayout();
/**
* 向上移动显示导航栏
*/
void showBottomLayout();
}

View File

@ -0,0 +1,128 @@
package me.majiajie.pagerbottomtabstrip;
import android.graphics.drawable.Drawable;
import me.majiajie.pagerbottomtabstrip.item.BaseTabItem;
import me.majiajie.pagerbottomtabstrip.listener.OnTabItemSelectedListener;
import me.majiajie.pagerbottomtabstrip.listener.SimpleTabItemSelectedListener;
public interface ItemController {
/**
* 设置选中项
*
* @param index 顺序索引
*/
void setSelect(int index);
/**
* 设置选中项并可以控制是否回调监听事件
*
* @param index 顺序索引
* @param listener true:假如存在监听事件{@link OnTabItemSelectedListener}就会调用相关的回调方法false:不会触发监听事件
*/
void setSelect(int index, boolean listener);
/**
* 设置导航按钮上显示的圆形消息数字通过顺序索引
*
* @param index 顺序索引
* @param number 消息数字
*/
void setMessageNumber(int index, int number);
/**
* 设置显示无数字的消息小原点
*
* @param index 顺序索引
* @param hasMessage true显示
*/
void setHasMessage(int index, boolean hasMessage);
/**
* 导航栏按钮点击监听
*
* @param listener {@link OnTabItemSelectedListener}
*/
void addTabItemSelectedListener(OnTabItemSelectedListener listener);
/**
* 导航栏按钮点击监听(只有选中事件)
*
* @param listener {@link SimpleTabItemSelectedListener}
*/
void addSimpleTabItemSelectedListener(SimpleTabItemSelectedListener listener);
/**
* 设置标题
*
* @param index 顺序索引
* @param title 标题文字
*/
void setTitle(int index, String title);
/**
* 设置未选中状态下的图标
*
* @param index 顺序索引
* @param drawable 图标资源
*/
void setDefaultDrawable(int index, Drawable drawable);
/**
* 设置选中状态下的图标
*
* @param index 顺序索引
* @param drawable 图标资源
*/
void setSelectedDrawable(int index, Drawable drawable);
/**
* 获取当前选中项索引
*
* @return 索引
*/
int getSelected();
/**
* 获取导航按钮总数
*
* @return 总数
*/
int getItemCount();
/**
* 获取导航按钮文字
*
* @param index 顺序索引
* @return 文字
*/
String getItemTitle(int index);
/**
* 移除指定的导航项.需要注意,不能移除当前选中的导航项
*
* @return 移除是否成功
*/
boolean removeItem(int index);
/**
* 添加一个材料设计样式的TabItem.注意,只对<code>material()</code>构建的导航栏有效
*
* @param index 顺序索引
* @param defaultDrawable 未选中状态的图标资源
* @param selectedDrawable 选中状态的图标资源
* @param title 标题
* @param selectedColor 选中状态的颜色
*/
void addMaterialItem(int index, Drawable defaultDrawable, Drawable selectedDrawable, String title, int selectedColor);
/**
* 添加一个自定义样式的TabItem.注意,只对<code>custom()</code>构建的导航栏有效
*
* @param index 顺序索引
* @param item 自定义的Item
*/
void addCustomItem(int index, BaseTabItem item);
}

View File

@ -0,0 +1,20 @@
package me.majiajie.pagerbottomtabstrip;
/**
* <p>模式选择</p>
* <p>采用组合的形式比如要两种效果可以这样做</p>
* MaterialMode.HIDE_TEXT | MaterialMode.CHANGE_BACKGROUND_COLOR
*/
public final class MaterialMode {
/**
* 隐藏文字内容只在选中时显示文字
*/
public static final int HIDE_TEXT = 0X1;
/**
* 开启导航栏背景变换点击不同项切换不同背景颜色
*/
public static final int CHANGE_BACKGROUND_COLOR = 0X2;
}

View File

@ -0,0 +1,112 @@
package me.majiajie.pagerbottomtabstrip;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.viewpager.widget.ViewPager;
import me.majiajie.pagerbottomtabstrip.internal.Utils;
import me.majiajie.pagerbottomtabstrip.item.BaseTabItem;
import me.majiajie.pagerbottomtabstrip.listener.OnTabItemSelectedListener;
import me.majiajie.pagerbottomtabstrip.listener.SimpleTabItemSelectedListener;
public class NavigationController implements ItemController, BottomLayoutController {
private BottomLayoutController mBottomLayoutController;
private ItemController mItemController;
protected NavigationController(BottomLayoutController bottomLayoutController, ItemController itemController) {
mBottomLayoutController = bottomLayoutController;
mItemController = itemController;
}
@Override
public void setSelect(int index) {
mItemController.setSelect(index);
}
@Override
public void setSelect(int index, boolean listener) {
mItemController.setSelect(index, listener);
}
@Override
public void setMessageNumber(int index, int number) {
mItemController.setMessageNumber(index, number);
}
@Override
public void setHasMessage(int index, boolean hasMessage) {
mItemController.setHasMessage(index, hasMessage);
}
@Override
public void addTabItemSelectedListener(@NonNull OnTabItemSelectedListener listener) {
mItemController.addTabItemSelectedListener(listener);
}
@Override
public void addSimpleTabItemSelectedListener(@NonNull SimpleTabItemSelectedListener listener) {
mItemController.addSimpleTabItemSelectedListener(listener);
}
@Override
public void setTitle(int index,@NonNull String title) {
mItemController.setTitle(index, title);
}
@Override
public void setDefaultDrawable(int index,@NonNull Drawable drawable) {
mItemController.setDefaultDrawable(index, drawable);
}
@Override
public void setSelectedDrawable(int index,@NonNull Drawable drawable) {
mItemController.setSelectedDrawable(index, drawable);
}
@Override
public int getSelected() {
return mItemController.getSelected();
}
@Override
public int getItemCount() {
return mItemController.getItemCount();
}
@Override
public String getItemTitle(int index) {
return mItemController.getItemTitle(index);
}
@Override
public boolean removeItem(int index) {
return mItemController.removeItem(index);
}
@Override
public void addMaterialItem(int index,@NonNull Drawable defaultDrawable,@NonNull Drawable selectedDrawable,@NonNull String title, int selectedColor) {
mItemController.addMaterialItem(index, Utils.newDrawable(defaultDrawable), Utils.newDrawable(selectedDrawable), title, selectedColor);
}
@Override
public void addCustomItem(int index,@NonNull BaseTabItem item) {
mItemController.addCustomItem(index, item);
}
@Override
public void setupWithViewPager(@NonNull ViewPager viewPager) {
mBottomLayoutController.setupWithViewPager(viewPager);
}
@Override
public void hideBottomLayout() {
mBottomLayoutController.hideBottomLayout();
}
@Override
public void showBottomLayout() {
mBottomLayoutController.showBottomLayout();
}
}

View File

@ -0,0 +1,666 @@
package me.majiajie.pagerbottomtabstrip;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.viewpager.widget.ViewPager;
import java.util.ArrayList;
import java.util.List;
import me.majiajie.pagerbottomtabstrip.internal.CustomItemVerticalLayout;
import me.majiajie.pagerbottomtabstrip.internal.MaterialItemLayout;
import me.majiajie.pagerbottomtabstrip.internal.MaterialItemVerticalLayout;
import me.majiajie.pagerbottomtabstrip.internal.OldCustomItemLayout;
import me.majiajie.pagerbottomtabstrip.internal.Utils;
import me.majiajie.pagerbottomtabstrip.item.BaseTabItem;
import me.majiajie.pagerbottomtabstrip.item.MaterialItemView;
import me.majiajie.pagerbottomtabstrip.item.OnlyIconMaterialItemView;
import me.majiajie.pagerbottomtabstrip.listener.OnTabItemSelectedListener;
/**
* 导航视图
*/
public class OldPageNavigationView extends ViewGroup {
private int mTabPaddingTop;
private int mTabPaddingBottom;
private NavigationController mNavigationController;
private ViewPagerPageChangeListener mPageChangeListener;
private ViewPager mViewPager;
private boolean mEnableVerticalLayout;
private OnTabItemSelectedListener mTabItemListener = new OnTabItemSelectedListener() {
@Override
public void onSelected(int index, int old) {
if (mViewPager != null) {
mViewPager.setCurrentItem(index, false);
}
}
@Override
public void onRepeat(int index) {
}
};
public OldPageNavigationView(Context context) {
this(context, null);
}
public OldPageNavigationView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public OldPageNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setPadding(0, 0, 0, 0);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.PageNavigationView);
if (a.hasValue(R.styleable.PageNavigationView_NavigationPaddingTop)) {
mTabPaddingTop = a.getDimensionPixelSize(R.styleable.PageNavigationView_NavigationPaddingTop, 0);
}
if (a.hasValue(R.styleable.PageNavigationView_NavigationPaddingBottom)) {
mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.PageNavigationView_NavigationPaddingBottom, 0);
}
a.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getChildCount();
int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
measureChild(child, widthMeasureSpec,heightMeasureSpec);
maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
}
setMeasuredDimension(maxWidth, maxHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
final int width = r - l;
final int height = b - t;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.layout(0, 0, width, height);
}
}
@Override
public CharSequence getAccessibilityClassName() {
return OldPageNavigationView.class.getName();
}
/**
* 构建 Material Desgin 风格的导航栏
*/
public MaterialBuilder material() {
return new MaterialBuilder();
}
/**
* 构建自定义导航栏
*/
public CustomBuilder custom() {
return new CustomBuilder();
}
/**
* 构建 自定义 的导航栏
*/
public class CustomBuilder {
private List<BaseTabItem> items;
private boolean enableVerticalLayout = false;
private boolean animateLayoutChanges = false;
CustomBuilder() {
items = new ArrayList<>();
}
/**
* 完成构建
*
* @return {@link NavigationController},通过它进行后续操作
* @throws RuntimeException 没有添加导航项时会抛出
*/
public NavigationController build() {
mEnableVerticalLayout = enableVerticalLayout;
//未添加任何按钮
if (items.size() == 0) {
throw new RuntimeException("must add a navigation item");
}
ItemController itemController;
if (enableVerticalLayout) {//垂直布局
CustomItemVerticalLayout verticalItemLayout = new CustomItemVerticalLayout(getContext());
verticalItemLayout.initialize(items, animateLayoutChanges);
verticalItemLayout.setPadding(0, mTabPaddingTop, 0, mTabPaddingBottom);
OldPageNavigationView.this.removeAllViews();
OldPageNavigationView.this.addView(verticalItemLayout);
itemController = verticalItemLayout;
} else {//水平布局
OldCustomItemLayout customItemLayout = new OldCustomItemLayout(getContext());
customItemLayout.initialize(items, animateLayoutChanges);
customItemLayout.setPadding(0, mTabPaddingTop, 0, mTabPaddingBottom);
OldPageNavigationView.this.removeAllViews();
OldPageNavigationView.this.addView(customItemLayout);
itemController = customItemLayout;
}
mNavigationController = new NavigationController(new Controller(), itemController);
mNavigationController.addTabItemSelectedListener(mTabItemListener);
return mNavigationController;
}
/**
* 添加一个导航按钮
*
* @param baseTabItem {@link BaseTabItem},所有自定义Item都必须继承它
* @return {@link CustomBuilder}
*/
public CustomBuilder addItem(BaseTabItem baseTabItem) {
items.add(baseTabItem);
return CustomBuilder.this;
}
/**
* 使用垂直布局
*
* @return {@link CustomBuilder}
*/
public CustomBuilder enableVerticalLayout() {
enableVerticalLayout = true;
return CustomBuilder.this;
}
/**
* 通过{@link NavigationController}动态移除/添加导航项时,显示默认的布局动画
*
* @return {@link CustomBuilder}
*/
public CustomBuilder enableAnimateLayoutChanges() {
animateLayoutChanges = true;
return CustomBuilder.this;
}
}
/**
* 构建 Material Desgin 风格的导航栏
*/
public class MaterialBuilder {
private final int DEFAULT_COLOR = 0x56000000;
private List<MaterialItemViewData> itemDatas;
private int defaultColor;
private int mode;
private int messageBackgroundColor;
private int messageNumberColor;
private boolean enableVerticalLayout = false;
private boolean doTintIcon = true;
private boolean animateLayoutChanges = false;
MaterialBuilder() {
itemDatas = new ArrayList<>();
}
/**
* 完成构建
*
* @return {@link NavigationController},通过它进行后续操作
* @throws RuntimeException 没有添加导航项时会抛出
*/
@NonNull
public NavigationController build() {
mEnableVerticalLayout = enableVerticalLayout;
// 未添加任何按钮
if (itemDatas.isEmpty()) {
throw new RuntimeException("must add a navigation item");
}
// 设置默认颜色
if (defaultColor == 0) {
defaultColor = DEFAULT_COLOR;
}
ItemController itemController;
if (enableVerticalLayout) {//垂直布局
List<BaseTabItem> items = new ArrayList<>();
for (MaterialItemViewData data : itemDatas) {
OnlyIconMaterialItemView materialItemView = new OnlyIconMaterialItemView(getContext());
materialItemView.initialization(data.title, data.drawable, data.checkedDrawable, doTintIcon, defaultColor, data.chekedColor);
//检查是否设置了消息圆点的颜色
if (messageBackgroundColor != 0) {
materialItemView.setMessageBackgroundColor(messageBackgroundColor);
}
//检查是否设置了消息数字的颜色
if (messageNumberColor != 0) {
materialItemView.setMessageNumberColor(messageNumberColor);
}
items.add(materialItemView);
}
MaterialItemVerticalLayout materialItemVerticalLayout = new MaterialItemVerticalLayout(getContext());
materialItemVerticalLayout.initialize(items, animateLayoutChanges, doTintIcon, defaultColor);
materialItemVerticalLayout.setPadding(0, mTabPaddingTop, 0, mTabPaddingBottom);
OldPageNavigationView.this.removeAllViews();
OldPageNavigationView.this.addView(materialItemVerticalLayout);
itemController = materialItemVerticalLayout;
} else {//水平布局
boolean changeBackground = (mode & MaterialMode.CHANGE_BACKGROUND_COLOR) > 0;
List<MaterialItemView> items = new ArrayList<>();
List<Integer> checkedColors = new ArrayList<>();
for (MaterialItemViewData data : itemDatas) {
// 记录设置的选中颜色
checkedColors.add(data.chekedColor);
MaterialItemView materialItemView = new MaterialItemView(getContext());
// 初始化Item,需要切换背景颜色就将选中颜色改成白色
materialItemView.initialization(data.title, data.drawable, data.checkedDrawable, doTintIcon, defaultColor, changeBackground ? Color.WHITE : data.chekedColor);
//检查是否设置了消息圆点的颜色
if (messageBackgroundColor != 0) {
materialItemView.setMessageBackgroundColor(messageBackgroundColor);
}
//检查是否设置了消息数字的颜色
if (messageNumberColor != 0) {
materialItemView.setMessageNumberColor(messageNumberColor);
}
items.add(materialItemView);
}
MaterialItemLayout materialItemLayout = new MaterialItemLayout(getContext());
materialItemLayout.initialize(items, checkedColors, mode, animateLayoutChanges, doTintIcon, defaultColor);
materialItemLayout.setPadding(0, mTabPaddingTop, 0, mTabPaddingBottom);
OldPageNavigationView.this.removeAllViews();
OldPageNavigationView.this.addView(materialItemLayout);
itemController = materialItemLayout;
}
mNavigationController = new NavigationController(new Controller(), itemController);
mNavigationController.addTabItemSelectedListener(mTabItemListener);
return mNavigationController;
}
/**
* 添加一个导航按钮
*
* @param drawableRes 图标资源
* @param title 显示文字内容.尽量简短
* @return {@link MaterialBuilder}
*/
public MaterialBuilder addItem(@DrawableRes int drawableRes, @NonNull String title) {
addItem(drawableRes, drawableRes, title, Utils.getColorPrimary(getContext()));
return MaterialBuilder.this;
}
/**
* 添加一个导航按钮
*
* @param drawableRes 图标资源
* @param checkedDrawableRes 选中时的图标资源
* @param title 显示文字内容.尽量简短
* @return {@link MaterialBuilder}
*/
public MaterialBuilder addItem(@DrawableRes int drawableRes, @DrawableRes int checkedDrawableRes, @NonNull String title) {
addItem(drawableRes, checkedDrawableRes, title, Utils.getColorPrimary(getContext()));
return MaterialBuilder.this;
}
/**
* 添加一个导航按钮
*
* @param drawableRes 图标资源
* @param title 显示文字内容.尽量简短
* @param chekedColor 选中的颜色
* @return {@link MaterialBuilder}
*/
public MaterialBuilder addItem(@DrawableRes int drawableRes, @NonNull String title, @ColorInt int chekedColor) {
addItem(drawableRes, drawableRes, title, chekedColor);
return MaterialBuilder.this;
}
/**
* 添加一个导航按钮
*
* @param drawableRes 图标资源
* @param checkedDrawableRes 选中时的图标资源
* @param title 显示文字内容.尽量简短
* @param chekedColor 选中的颜色
* @return {@link MaterialBuilder}
* @throws Resources.NotFoundException drawable 资源获取异常
*/
public MaterialBuilder addItem(@DrawableRes int drawableRes, @DrawableRes int checkedDrawableRes, @NonNull String title, @ColorInt int chekedColor) {
Drawable defaultDrawable = ContextCompat.getDrawable(getContext(), drawableRes);
Drawable checkDrawable = ContextCompat.getDrawable(getContext(), checkedDrawableRes);
if (defaultDrawable == null) {
throw new Resources.NotFoundException("Resource ID " + Integer.toHexString(drawableRes));
}
if (checkDrawable == null) {
throw new Resources.NotFoundException("Resource ID " + Integer.toHexString(checkedDrawableRes));
}
addItem(defaultDrawable, checkDrawable, title, chekedColor);
return MaterialBuilder.this;
}
/**
* 添加一个导航按钮
*
* @param drawable 图标资源
* @param title 显示文字内容.尽量简短
* @return {@link MaterialBuilder}
*/
public MaterialBuilder addItem(@NonNull Drawable drawable, @NonNull String title) {
addItem(drawable, drawable, title, Utils.getColorPrimary(getContext()));
return MaterialBuilder.this;
}
/**
* 添加一个导航按钮
*
* @param drawable 图标资源
* @param checkedDrawable 选中时的图标资源
* @param title 显示文字内容.尽量简短
* @return {@link MaterialBuilder}
*/
public MaterialBuilder addItem(@NonNull Drawable drawable, @NonNull Drawable checkedDrawable, @NonNull String title) {
addItem(drawable, checkedDrawable, title, Utils.getColorPrimary(getContext()));
return MaterialBuilder.this;
}
/**
* 添加一个导航按钮
*
* @param drawable 图标资源
* @param title 显示文字内容.尽量简短
* @param chekedColor 选中的颜色
* @return {@link MaterialBuilder}
*/
public MaterialBuilder addItem(@NonNull Drawable drawable, @NonNull String title, @ColorInt int chekedColor) {
addItem(drawable, drawable, title, chekedColor);
return MaterialBuilder.this;
}
/**
* 添加一个导航按钮
*
* @param drawable 图标资源
* @param checkedDrawable 选中时的图标资源
* @param title 显示文字内容.尽量简短
* @param chekedColor 选中的颜色
* @return {@link MaterialBuilder}
*/
public MaterialBuilder addItem(@NonNull Drawable drawable, @NonNull Drawable checkedDrawable, @NonNull String title, @ColorInt int chekedColor) {
MaterialItemViewData data = new MaterialItemViewData();
data.drawable = Utils.newDrawable(drawable);
data.checkedDrawable = Utils.newDrawable(checkedDrawable);
data.title = title;
data.chekedColor = chekedColor;
itemDatas.add(data);
return MaterialBuilder.this;
}
/**
* 设置导航按钮的默认未选中状态颜色
*
* @param color 16进制整形表示的颜色例如红色0xFFFF0000
* @return {@link MaterialBuilder}
*/
public MaterialBuilder setDefaultColor(@ColorInt int color) {
defaultColor = color;
return MaterialBuilder.this;
}
/**
* 设置消息圆点的颜色
*
* @param color 16进制整形表示的颜色例如红色0xFFFF0000
* @return {@link MaterialBuilder}
*/
public MaterialBuilder setMessageBackgroundColor(@ColorInt int color) {
messageBackgroundColor = color;
return MaterialBuilder.this;
}
/**
* 设置消息数字的颜色
*
* @param color 16进制整形表示的颜色例如红色0xFFFF0000
* @return {@link MaterialBuilder}
*/
public MaterialBuilder setMessageNumberColor(@ColorInt int color) {
messageNumberColor = color;
return MaterialBuilder.this;
}
/**
* 设置模式(在垂直布局中无效)默认文字一直显示且背景色不变
* 可以通过{@link MaterialMode}选择模式
* <p>例如:</p>
* {@code MaterialMode.HIDE_TEXT}
* <p>或者多选:</p>
* {@code MaterialMode.HIDE_TEXT | MaterialMode.CHANGE_BACKGROUND_COLOR}
*
* @param mode {@link MaterialMode}
* @return {@link MaterialBuilder}
*/
public MaterialBuilder setMode(int mode) {
MaterialBuilder.this.mode = mode;
return MaterialBuilder.this;
}
/**
* 使用垂直布局
*
* @return {@link MaterialBuilder}
*/
public MaterialBuilder enableVerticalLayout() {
enableVerticalLayout = true;
return MaterialBuilder.this;
}
/**
* 不对图标进行染色
*
* @return {@link MaterialBuilder}
*/
public MaterialBuilder dontTintIcon() {
doTintIcon = false;
return MaterialBuilder.this;
}
/**
* 通过{@link NavigationController}动态移除/添加导航项时,显示默认的布局动画
*
* @return {@link MaterialBuilder}
*/
public MaterialBuilder enableAnimateLayoutChanges() {
animateLayoutChanges = true;
return MaterialBuilder.this;
}
}
/**
* 材料设计的单项视图信息
*/
private static class MaterialItemViewData {
Drawable drawable;
Drawable checkedDrawable;
String title;
@ColorInt
int chekedColor;
}
/**
* 实现控制接口
*/
private class Controller implements BottomLayoutController {
private ObjectAnimator animator;
private boolean hide = false;
@Override
public void setupWithViewPager(ViewPager viewPager) {
if (viewPager == null) {
return;
}
mViewPager = viewPager;
if (mPageChangeListener != null) {
mViewPager.removeOnPageChangeListener(mPageChangeListener);
} else {
mPageChangeListener = new ViewPagerPageChangeListener();
}
if (mNavigationController != null) {
int n = mViewPager.getCurrentItem();
if (mNavigationController.getSelected() != n) {
mNavigationController.setSelect(n);
}
mViewPager.addOnPageChangeListener(mPageChangeListener);
}
}
@Override
public void hideBottomLayout() {
if (!hide) {
hide = true;
getAnimator().start();
}
}
@Override
public void showBottomLayout() {
if (hide) {
hide = false;
getAnimator().reverse();
}
}
private ObjectAnimator getAnimator() {
if (animator == null) {
if (mEnableVerticalLayout) {// 垂直布局向左隐藏
animator = ObjectAnimator.ofFloat(
OldPageNavigationView.this, "translationX", 0, -OldPageNavigationView.this.getWidth());
} else {// 水平布局向下隐藏
animator = ObjectAnimator.ofFloat(
OldPageNavigationView.this, "translationY", 0, OldPageNavigationView.this.getHeight());
}
animator.setDuration(300);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
}
return animator;
}
}
private class ViewPagerPageChangeListener implements ViewPager.OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
if (mNavigationController != null && mNavigationController.getSelected() != position) {
mNavigationController.setSelect(position);
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
}
private static final String INSTANCE_STATUS = "INSTANCE_STATUS";
private final String STATUS_SELECTED = "STATUS_SELECTED";
@Override
protected Parcelable onSaveInstanceState() {
if (mNavigationController == null) {
return super.onSaveInstanceState();
}
Bundle bundle = new Bundle();
bundle.putParcelable(INSTANCE_STATUS, super.onSaveInstanceState());
bundle.putInt(STATUS_SELECTED, mNavigationController.getSelected());
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
int selected = bundle.getInt(STATUS_SELECTED, 0);
super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATUS));
if (selected != 0 && mNavigationController != null) {
mNavigationController.setSelect(selected);
}
return;
}
super.onRestoreInstanceState(state);
}
}

View File

@ -0,0 +1,665 @@
package me.majiajie.pagerbottomtabstrip;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.viewpager.widget.ViewPager;
import me.majiajie.pagerbottomtabstrip.internal.CustomItemLayout;
import me.majiajie.pagerbottomtabstrip.internal.CustomItemVerticalLayout;
import me.majiajie.pagerbottomtabstrip.internal.MaterialItemLayout;
import me.majiajie.pagerbottomtabstrip.internal.MaterialItemVerticalLayout;
import me.majiajie.pagerbottomtabstrip.internal.Utils;
import me.majiajie.pagerbottomtabstrip.item.BaseTabItem;
import me.majiajie.pagerbottomtabstrip.item.MaterialItemView;
import me.majiajie.pagerbottomtabstrip.item.OnlyIconMaterialItemView;
import me.majiajie.pagerbottomtabstrip.listener.OnTabItemSelectedListener;
/**
* 导航视图
*/
public class PageNavigationView extends ViewGroup {
private int mTabPaddingTop;
private int mTabPaddingBottom;
private NavigationController mNavigationController;
private ViewPagerPageChangeListener mPageChangeListener;
private ViewPager mViewPager;
private boolean mEnableVerticalLayout;
private OnTabItemSelectedListener mTabItemListener = new OnTabItemSelectedListener() {
@Override
public void onSelected(int index, int old) {
if (mViewPager != null) {
mViewPager.setCurrentItem(index, false);
}
}
@Override
public void onRepeat(int index) {
}
};
public PageNavigationView(Context context) {
this(context, null);
}
public PageNavigationView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PageNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setPadding(0, 0, 0, 0);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.PageNavigationView);
if (a.hasValue(R.styleable.PageNavigationView_NavigationPaddingTop)) {
mTabPaddingTop = a.getDimensionPixelSize(R.styleable.PageNavigationView_NavigationPaddingTop, 0);
}
if (a.hasValue(R.styleable.PageNavigationView_NavigationPaddingBottom)) {
mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.PageNavigationView_NavigationPaddingBottom, 0);
}
a.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getChildCount();
int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
measureChild(child, widthMeasureSpec,heightMeasureSpec);
maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
}
setMeasuredDimension(maxWidth, maxHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
final int width = r - l;
final int height = b - t;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.layout(0, 0, width, height);
}
}
@Override
public CharSequence getAccessibilityClassName() {
return PageNavigationView.class.getName();
}
/**
* 构建 Material Desgin 风格的导航栏
*/
public MaterialBuilder material() {
return new MaterialBuilder();
}
/**
* 构建自定义导航栏
*/
public CustomBuilder custom() {
return new CustomBuilder();
}
/**
* 构建 自定义 的导航栏
*/
public class CustomBuilder {
private List<BaseTabItem> items;
private boolean enableVerticalLayout = false;
private boolean animateLayoutChanges = false;
CustomBuilder() {
items = new ArrayList<>();
}
/**
* 完成构建
*
* @return {@link NavigationController},通过它进行后续操作
* @throws RuntimeException 没有添加导航项时会抛出
*/
public NavigationController build() {
mEnableVerticalLayout = enableVerticalLayout;
//未添加任何按钮
if (items.size() == 0) {
throw new RuntimeException("must add a navigation item");
}
ItemController itemController;
if (enableVerticalLayout) {//垂直布局
CustomItemVerticalLayout verticalItemLayout = new CustomItemVerticalLayout(getContext());
verticalItemLayout.initialize(items, animateLayoutChanges);
verticalItemLayout.setPadding(0, mTabPaddingTop, 0, mTabPaddingBottom);
PageNavigationView.this.removeAllViews();
PageNavigationView.this.addView(verticalItemLayout);
itemController = verticalItemLayout;
} else {//水平布局
CustomItemLayout customItemLayout = new CustomItemLayout(getContext());
customItemLayout.initialize(items, animateLayoutChanges);
customItemLayout.setPadding(0, mTabPaddingTop, 0, mTabPaddingBottom);
PageNavigationView.this.removeAllViews();
PageNavigationView.this.addView(customItemLayout);
itemController = customItemLayout;
}
mNavigationController = new NavigationController(new Controller(), itemController);
mNavigationController.addTabItemSelectedListener(mTabItemListener);
return mNavigationController;
}
/**
* 添加一个导航按钮
*
* @param baseTabItem {@link BaseTabItem},所有自定义Item都必须继承它
* @return {@link CustomBuilder}
*/
public CustomBuilder addItem(BaseTabItem baseTabItem) {
items.add(baseTabItem);
return CustomBuilder.this;
}
/**
* 使用垂直布局
*
* @return {@link CustomBuilder}
*/
public CustomBuilder enableVerticalLayout() {
enableVerticalLayout = true;
return CustomBuilder.this;
}
/**
* 通过{@link NavigationController}动态移除/添加导航项时,显示默认的布局动画
*
* @return {@link CustomBuilder}
*/
public CustomBuilder enableAnimateLayoutChanges() {
animateLayoutChanges = true;
return CustomBuilder.this;
}
}
/**
* 构建 Material Desgin 风格的导航栏
*/
public class MaterialBuilder {
private final int DEFAULT_COLOR = 0x56000000;
private List<MaterialItemViewData> itemDatas;
private int defaultColor;
private int mode;
private int messageBackgroundColor;
private int messageNumberColor;
private boolean enableVerticalLayout = false;
private boolean doTintIcon = true;
private boolean animateLayoutChanges = false;
MaterialBuilder() {
itemDatas = new ArrayList<>();
}
/**
* 完成构建
*
* @return {@link NavigationController},通过它进行后续操作
* @throws RuntimeException 没有添加导航项时会抛出
*/
@NonNull
public NavigationController build() {
mEnableVerticalLayout = enableVerticalLayout;
// 未添加任何按钮
if (itemDatas.isEmpty()) {
throw new RuntimeException("must add a navigation item");
}
// 设置默认颜色
if (defaultColor == 0) {
defaultColor = DEFAULT_COLOR;
}
ItemController itemController;
if (enableVerticalLayout) {//垂直布局
List<BaseTabItem> items = new ArrayList<>();
for (MaterialItemViewData data : itemDatas) {
OnlyIconMaterialItemView materialItemView = new OnlyIconMaterialItemView(getContext());
materialItemView.initialization(data.title, data.drawable, data.checkedDrawable, doTintIcon, defaultColor, data.chekedColor);
//检查是否设置了消息圆点的颜色
if (messageBackgroundColor != 0) {
materialItemView.setMessageBackgroundColor(messageBackgroundColor);
}
//检查是否设置了消息数字的颜色
if (messageNumberColor != 0) {
materialItemView.setMessageNumberColor(messageNumberColor);
}
items.add(materialItemView);
}
MaterialItemVerticalLayout materialItemVerticalLayout = new MaterialItemVerticalLayout(getContext());
materialItemVerticalLayout.initialize(items, animateLayoutChanges, doTintIcon, defaultColor);
materialItemVerticalLayout.setPadding(0, mTabPaddingTop, 0, mTabPaddingBottom);
PageNavigationView.this.removeAllViews();
PageNavigationView.this.addView(materialItemVerticalLayout);
itemController = materialItemVerticalLayout;
} else {//水平布局
boolean changeBackground = (mode & MaterialMode.CHANGE_BACKGROUND_COLOR) > 0;
List<MaterialItemView> items = new ArrayList<>();
List<Integer> checkedColors = new ArrayList<>();
for (MaterialItemViewData data : itemDatas) {
// 记录设置的选中颜色
checkedColors.add(data.chekedColor);
MaterialItemView materialItemView = new MaterialItemView(getContext());
// 初始化Item,需要切换背景颜色就将选中颜色改成白色
materialItemView.initialization(data.title, data.drawable, data.checkedDrawable, doTintIcon, defaultColor, changeBackground ? Color.WHITE : data.chekedColor);
//检查是否设置了消息圆点的颜色
if (messageBackgroundColor != 0) {
materialItemView.setMessageBackgroundColor(messageBackgroundColor);
}
//检查是否设置了消息数字的颜色
if (messageNumberColor != 0) {
materialItemView.setMessageNumberColor(messageNumberColor);
}
items.add(materialItemView);
}
MaterialItemLayout materialItemLayout = new MaterialItemLayout(getContext());
materialItemLayout.initialize(items, checkedColors, mode, animateLayoutChanges, doTintIcon, defaultColor);
materialItemLayout.setPadding(0, mTabPaddingTop, 0, mTabPaddingBottom);
PageNavigationView.this.removeAllViews();
PageNavigationView.this.addView(materialItemLayout);
itemController = materialItemLayout;
}
mNavigationController = new NavigationController(new Controller(), itemController);
mNavigationController.addTabItemSelectedListener(mTabItemListener);
return mNavigationController;
}
/**
* 添加一个导航按钮
*
* @param drawableRes 图标资源
* @param title 显示文字内容.尽量简短
* @return {@link MaterialBuilder}
*/
public MaterialBuilder addItem(@DrawableRes int drawableRes, @NonNull String title) {
addItem(drawableRes, drawableRes, title, Utils.getColorPrimary(getContext()));
return MaterialBuilder.this;
}
/**
* 添加一个导航按钮
*
* @param drawableRes 图标资源
* @param checkedDrawableRes 选中时的图标资源
* @param title 显示文字内容.尽量简短
* @return {@link MaterialBuilder}
*/
public MaterialBuilder addItem(@DrawableRes int drawableRes, @DrawableRes int checkedDrawableRes, @NonNull String title) {
addItem(drawableRes, checkedDrawableRes, title, Utils.getColorPrimary(getContext()));
return MaterialBuilder.this;
}
/**
* 添加一个导航按钮
*
* @param drawableRes 图标资源
* @param title 显示文字内容.尽量简短
* @param chekedColor 选中的颜色
* @return {@link MaterialBuilder}
*/
public MaterialBuilder addItem(@DrawableRes int drawableRes, @NonNull String title, @ColorInt int chekedColor) {
addItem(drawableRes, drawableRes, title, chekedColor);
return MaterialBuilder.this;
}
/**
* 添加一个导航按钮
*
* @param drawableRes 图标资源
* @param checkedDrawableRes 选中时的图标资源
* @param title 显示文字内容.尽量简短
* @param chekedColor 选中的颜色
* @return {@link MaterialBuilder}
* @throws Resources.NotFoundException drawable 资源获取异常
*/
public MaterialBuilder addItem(@DrawableRes int drawableRes, @DrawableRes int checkedDrawableRes, @NonNull String title, @ColorInt int chekedColor) {
Drawable defaultDrawable = ContextCompat.getDrawable(getContext(), drawableRes);
Drawable checkDrawable = ContextCompat.getDrawable(getContext(), checkedDrawableRes);
if (defaultDrawable == null) {
throw new Resources.NotFoundException("Resource ID " + Integer.toHexString(drawableRes));
}
if (checkDrawable == null) {
throw new Resources.NotFoundException("Resource ID " + Integer.toHexString(checkedDrawableRes));
}
addItem(defaultDrawable, checkDrawable, title, chekedColor);
return MaterialBuilder.this;
}
/**
* 添加一个导航按钮
*
* @param drawable 图标资源
* @param title 显示文字内容.尽量简短
* @return {@link MaterialBuilder}
*/
public MaterialBuilder addItem(@NonNull Drawable drawable, @NonNull String title) {
addItem(drawable, drawable, title, Utils.getColorPrimary(getContext()));
return MaterialBuilder.this;
}
/**
* 添加一个导航按钮
*
* @param drawable 图标资源
* @param checkedDrawable 选中时的图标资源
* @param title 显示文字内容.尽量简短
* @return {@link MaterialBuilder}
*/
public MaterialBuilder addItem(@NonNull Drawable drawable, @NonNull Drawable checkedDrawable, @NonNull String title) {
addItem(drawable, checkedDrawable, title, Utils.getColorPrimary(getContext()));
return MaterialBuilder.this;
}
/**
* 添加一个导航按钮
*
* @param drawable 图标资源
* @param title 显示文字内容.尽量简短
* @param chekedColor 选中的颜色
* @return {@link MaterialBuilder}
*/
public MaterialBuilder addItem(@NonNull Drawable drawable, @NonNull String title, @ColorInt int chekedColor) {
addItem(drawable, drawable, title, chekedColor);
return MaterialBuilder.this;
}
/**
* 添加一个导航按钮
*
* @param drawable 图标资源
* @param checkedDrawable 选中时的图标资源
* @param title 显示文字内容.尽量简短
* @param chekedColor 选中的颜色
* @return {@link MaterialBuilder}
*/
public MaterialBuilder addItem(@NonNull Drawable drawable, @NonNull Drawable checkedDrawable, @NonNull String title, @ColorInt int chekedColor) {
MaterialItemViewData data = new MaterialItemViewData();
data.drawable = Utils.newDrawable(drawable);
data.checkedDrawable = Utils.newDrawable(checkedDrawable);
data.title = title;
data.chekedColor = chekedColor;
itemDatas.add(data);
return MaterialBuilder.this;
}
/**
* 设置导航按钮的默认未选中状态颜色
*
* @param color 16进制整形表示的颜色例如红色0xFFFF0000
* @return {@link MaterialBuilder}
*/
public MaterialBuilder setDefaultColor(@ColorInt int color) {
defaultColor = color;
return MaterialBuilder.this;
}
/**
* 设置消息圆点的颜色
*
* @param color 16进制整形表示的颜色例如红色0xFFFF0000
* @return {@link MaterialBuilder}
*/
public MaterialBuilder setMessageBackgroundColor(@ColorInt int color) {
messageBackgroundColor = color;
return MaterialBuilder.this;
}
/**
* 设置消息数字的颜色
*
* @param color 16进制整形表示的颜色例如红色0xFFFF0000
* @return {@link MaterialBuilder}
*/
public MaterialBuilder setMessageNumberColor(@ColorInt int color) {
messageNumberColor = color;
return MaterialBuilder.this;
}
/**
* 设置模式(在垂直布局中无效)默认文字一直显示且背景色不变
* 可以通过{@link MaterialMode}选择模式
* <p>例如:</p>
* {@code MaterialMode.HIDE_TEXT}
* <p>或者多选:</p>
* {@code MaterialMode.HIDE_TEXT | MaterialMode.CHANGE_BACKGROUND_COLOR}
*
* @param mode {@link MaterialMode}
* @return {@link MaterialBuilder}
*/
public MaterialBuilder setMode(int mode) {
MaterialBuilder.this.mode = mode;
return MaterialBuilder.this;
}
/**
* 使用垂直布局
*
* @return {@link MaterialBuilder}
*/
public MaterialBuilder enableVerticalLayout() {
enableVerticalLayout = true;
return MaterialBuilder.this;
}
/**
* 不对图标进行染色
*
* @return {@link MaterialBuilder}
*/
public MaterialBuilder dontTintIcon() {
doTintIcon = false;
return MaterialBuilder.this;
}
/**
* 通过{@link NavigationController}动态移除/添加导航项时,显示默认的布局动画
*
* @return {@link MaterialBuilder}
*/
public MaterialBuilder enableAnimateLayoutChanges() {
animateLayoutChanges = true;
return MaterialBuilder.this;
}
}
/**
* 材料设计的单项视图信息
*/
private static class MaterialItemViewData {
Drawable drawable;
Drawable checkedDrawable;
String title;
@ColorInt
int chekedColor;
}
/**
* 实现控制接口
*/
private class Controller implements BottomLayoutController {
private ObjectAnimator animator;
private boolean hide = false;
@Override
public void setupWithViewPager(ViewPager viewPager) {
if (viewPager == null) {
return;
}
mViewPager = viewPager;
if (mPageChangeListener != null) {
mViewPager.removeOnPageChangeListener(mPageChangeListener);
} else {
mPageChangeListener = new ViewPagerPageChangeListener();
}
if (mNavigationController != null) {
int n = mViewPager.getCurrentItem();
if (mNavigationController.getSelected() != n) {
mNavigationController.setSelect(n);
}
mViewPager.addOnPageChangeListener(mPageChangeListener);
}
}
@Override
public void hideBottomLayout() {
if (!hide) {
hide = true;
getAnimator().start();
}
}
@Override
public void showBottomLayout() {
if (hide) {
hide = false;
getAnimator().reverse();
}
}
private ObjectAnimator getAnimator() {
if (animator == null) {
if (mEnableVerticalLayout) {// 垂直布局向左隐藏
animator = ObjectAnimator.ofFloat(
PageNavigationView.this, "translationX", 0, -PageNavigationView.this.getWidth());
} else {// 水平布局向下隐藏
animator = ObjectAnimator.ofFloat(
PageNavigationView.this, "translationY", 0, PageNavigationView.this.getHeight());
}
animator.setDuration(300);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
}
return animator;
}
}
private class ViewPagerPageChangeListener implements ViewPager.OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
if (mNavigationController != null && mNavigationController.getSelected() != position) {
mNavigationController.setSelect(position);
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
}
private static final String INSTANCE_STATUS = "INSTANCE_STATUS";
private final String STATUS_SELECTED = "STATUS_SELECTED";
@Override
protected Parcelable onSaveInstanceState() {
if (mNavigationController == null) {
return super.onSaveInstanceState();
}
Bundle bundle = new Bundle();
bundle.putParcelable(INSTANCE_STATUS, super.onSaveInstanceState());
bundle.putInt(STATUS_SELECTED, mNavigationController.getSelected());
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
int selected = bundle.getInt(STATUS_SELECTED, 0);
super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATUS));
if (selected != 0 && mNavigationController != null) {
mNavigationController.setSelect(selected);
}
return;
}
super.onRestoreInstanceState(state);
}
}

View File

@ -0,0 +1,97 @@
package me.majiajie.pagerbottomtabstrip.internal;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* A simple ViewGroup that aligns all the views inside on a baseline. Note: bottom padding for this
* view will be measured starting from the baseline.
*/
public class BaselineLayout extends ViewGroup {
private int mBaseline = -1;
public BaselineLayout(Context context) {
super(context, null, 0);
}
public BaselineLayout(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
public BaselineLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getChildCount();
int maxWidth = 0;
int maxHeight = 0;
int maxChildBaseline = -1;
int maxChildDescent = -1;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
measureChild(child, widthMeasureSpec, heightMeasureSpec);
final int baseline = child.getBaseline();
if (baseline != -1) {
maxChildBaseline = Math.max(maxChildBaseline, baseline);
maxChildDescent = Math.max(maxChildDescent, child.getMeasuredHeight() - baseline);
}
maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
childState = childState | child.getMeasuredState();
}
if (maxChildBaseline != -1) {
maxChildDescent = Math.max(maxChildDescent, getPaddingBottom());
maxHeight = Math.max(maxHeight, maxChildBaseline + maxChildDescent);
mBaseline = maxChildBaseline;
}
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
setMeasuredDimension(
View.resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
View.resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
final int parentLeft = getPaddingLeft();
final int parentRight = right - left - getPaddingRight();
final int parentContentWidth = parentRight - parentLeft;
final int parentTop = getPaddingTop();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
final int childLeft = parentLeft + (parentContentWidth - width) / 2;
final int childTop;
if (mBaseline != -1 && child.getBaseline() != -1) {
childTop = parentTop + mBaseline - child.getBaseline();
} else {
childTop = parentTop;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
@Override
public int getBaseline() {
return mBaseline;
}
}

View File

@ -0,0 +1,277 @@
package me.majiajie.pagerbottomtabstrip.internal;
import android.animation.LayoutTransition;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
import androidx.core.view.ViewCompat;
import me.majiajie.pagerbottomtabstrip.ItemController;
import me.majiajie.pagerbottomtabstrip.item.BaseTabItem;
import me.majiajie.pagerbottomtabstrip.listener.OnTabItemSelectedListener;
import me.majiajie.pagerbottomtabstrip.listener.SimpleTabItemSelectedListener;
/**
* 存放自定义项的布局
*/
public class CustomItemLayout extends ViewGroup implements ItemController {
private final List<BaseTabItem> mItems = new ArrayList<>();
private final List<OnTabItemSelectedListener> mListeners = new ArrayList<>();
private final List<SimpleTabItemSelectedListener> mSimpleListeners = new ArrayList<>();
private int mSelected = -1;
public CustomItemLayout(Context context) {
this(context, null);
}
public CustomItemLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomItemLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayoutTransition(new LayoutTransition());
}
public void initialize(List<BaseTabItem> items, boolean animateLayoutChanges) {
mItems.clear();
mItems.addAll(items);
if (animateLayoutChanges) {
setLayoutTransition(new LayoutTransition());
}
//添加按钮到布局并注册点击事件
int n = mItems.size();
for (int i = 0; i < n; i++) {
final BaseTabItem tabItem = mItems.get(i);
tabItem.setChecked(false);
this.addView(tabItem);
tabItem.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int index = mItems.indexOf(tabItem);
if (index >= 0) {
setSelect(index);
}
}
});
}
//默认选中第一项
mSelected = 0;
mItems.get(0).setChecked(true);
}
/**
* dip转化像素
* @param dipValue
* @return
*/
public static int dip2px(float dipValue){
final float scale = Resources.getSystem().getDisplayMetrics().density;
return (int)(dipValue * scale + 0.5f);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int n = getChildCount();
int visableChildCount = 0;
for (int i = 0; i < n; i++) {
if (getChildAt(i).getVisibility() != GONE) {
visableChildCount++;
}
}
if (visableChildCount == 0) {
return;
}
final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec) / visableChildCount, MeasureSpec.EXACTLY);
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(0, MeasureSpec.getSize(heightMeasureSpec) - getPaddingBottom() - getPaddingTop()), MeasureSpec.EXACTLY);
for (int i = 0; i < n; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.measure(widthMeasureSpec, childHeightMeasureSpec);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
final int width = right - left;
final int height = bottom - top;
//只支持topbottom的padding
final int padding_top = getPaddingTop();
final int padding_bottom = getPaddingBottom();
int used = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) {
child.layout(width - used - child.getMeasuredWidth(), padding_top, width - used, height - padding_bottom);
} else {
child.layout(used, padding_top, child.getMeasuredWidth() + used, height - padding_bottom);
}
used += child.getMeasuredWidth()+dip2px(33);
}
}
@Override
public CharSequence getAccessibilityClassName() {
return CustomItemLayout.class.getName();
}
@Override
public void setSelect(int index) {
setSelect(index, true);
}
@Override
public void setSelect(int index, boolean needListener) {
//重复选择
if (index == mSelected) {
if (needListener) {
for (OnTabItemSelectedListener listener : mListeners) {
mItems.get(mSelected).onRepeat();
listener.onRepeat(mSelected);
}
}
return;
}
//记录前一个选中项和当前选中项
int oldSelected = mSelected;
mSelected = index;
//前一个选中项必须不小于0才有效
if (oldSelected >= 0) {
mItems.get(oldSelected).setChecked(false);
}
mItems.get(mSelected).setChecked(true);
if (needListener) {
//事件回调
for (OnTabItemSelectedListener listener : mListeners) {
listener.onSelected(mSelected, oldSelected);
}
for (SimpleTabItemSelectedListener listener : mSimpleListeners) {
listener.onSelected(mSelected, oldSelected);
}
}
}
@Override
public void setMessageNumber(int index, int number) {
mItems.get(index).setMessageNumber(number);
}
@Override
public void setHasMessage(int index, boolean hasMessage) {
mItems.get(index).setHasMessage(hasMessage);
}
@Override
public void addTabItemSelectedListener(OnTabItemSelectedListener listener) {
mListeners.add(listener);
}
@Override
public void addSimpleTabItemSelectedListener(SimpleTabItemSelectedListener listener) {
mSimpleListeners.add(listener);
}
@Override
public void setTitle(int index, String title) {
mItems.get(index).setTitle(title);
}
@Override
public void setDefaultDrawable(int index, Drawable drawable) {
mItems.get(index).setDefaultDrawable(drawable);
}
@Override
public void setSelectedDrawable(int index, Drawable drawable) {
mItems.get(index).setSelectedDrawable(drawable);
}
@Override
public int getSelected() {
return mSelected;
}
@Override
public int getItemCount() {
return mItems.size();
}
@Override
public String getItemTitle(int index) {
return mItems.get(index).getTitle();
}
@Override
public boolean removeItem(int index) {
if (index == mSelected || index >= mItems.size() || index < 0) {
return false;
}
if (mSelected > index) {
mSelected--;
}
this.removeViewAt(index);
mItems.remove(index);
return true;
}
@Override
public void addMaterialItem(int index, Drawable defaultDrawable, Drawable selectedDrawable, String title, int selectedColor) {
// nothing
}
@Override
public void addCustomItem(int index, final BaseTabItem item) {
item.setChecked(false);
item.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int index = mItems.indexOf(item);
if (index >= 0) {
setSelect(index);
}
}
});
if (index >= mItems.size()) {
mItems.add(index, item);
this.addView(item);
} else {
mItems.add(index, item);
this.addView(item, index);
}
}
}

View File

@ -0,0 +1,254 @@
package me.majiajie.pagerbottomtabstrip.internal;
import android.animation.LayoutTransition;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
import me.majiajie.pagerbottomtabstrip.ItemController;
import me.majiajie.pagerbottomtabstrip.item.BaseTabItem;
import me.majiajie.pagerbottomtabstrip.listener.OnTabItemSelectedListener;
import me.majiajie.pagerbottomtabstrip.listener.SimpleTabItemSelectedListener;
/**
* Created by mjj on 2017/9/27
*/
public class CustomItemVerticalLayout extends ViewGroup implements ItemController {
private final List<BaseTabItem> mItems = new ArrayList<>();
private final List<OnTabItemSelectedListener> mListeners = new ArrayList<>();
private final List<SimpleTabItemSelectedListener> mSimpleListeners = new ArrayList<>();
private int mSelected = -1;
public CustomItemVerticalLayout(Context context) {
this(context, null);
}
public CustomItemVerticalLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomItemVerticalLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayoutTransition(new LayoutTransition());
}
public void initialize(List<BaseTabItem> items, boolean animateLayoutChanges) {
mItems.clear();
mItems.addAll(items);
if (animateLayoutChanges) {
setLayoutTransition(new LayoutTransition());
}
//添加按钮到布局并注册点击事件
int n = mItems.size();
for (int i = 0; i < n; i++) {
final BaseTabItem tabItem = mItems.get(i);
tabItem.setChecked(false);
this.addView(tabItem);
tabItem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int index = mItems.indexOf(tabItem);
if (index >= 0) {
setSelect(index);
}
}
});
}
//默认选中第一项
mSelected = 0;
mItems.get(0).setChecked(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int parentHeightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED);
final int childwidthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY);
int totalHeight = getPaddingTop() + getPaddingBottom();
final int n = getChildCount();
for (int i = 0; i < n; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
final LayoutParams lp = child.getLayoutParams();
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
getPaddingTop() + getPaddingBottom(), lp.height);
child.measure(childwidthMeasureSpec, childHeightMeasureSpec);
totalHeight += child.getMeasuredHeight();
}
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), totalHeight);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
//只支持top的padding
int used = getPaddingTop();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.layout(0, used, child.getMeasuredWidth(), used + child.getMeasuredHeight());
used += child.getMeasuredHeight();
}
}
@Override
public CharSequence getAccessibilityClassName() {
return CustomItemVerticalLayout.class.getName();
}
@Override
public void setSelect(int index) {
setSelect(index, true);
}
@Override
public void setSelect(int index, boolean needListener) {
//重复选择
if (index == mSelected) {
if (needListener) {
for (OnTabItemSelectedListener listener : mListeners) {
mItems.get(mSelected).onRepeat();
listener.onRepeat(mSelected);
}
}
return;
}
//记录前一个选中项和当前选中项
int oldSelected = mSelected;
mSelected = index;
//前一个选中项必须不小于0才有效
if (oldSelected >= 0) {
mItems.get(oldSelected).setChecked(false);
}
mItems.get(mSelected).setChecked(true);
if (needListener) {
//事件回调
for (OnTabItemSelectedListener listener : mListeners) {
listener.onSelected(mSelected, oldSelected);
}
for (SimpleTabItemSelectedListener listener : mSimpleListeners) {
listener.onSelected(mSelected, oldSelected);
}
}
}
@Override
public void setMessageNumber(int index, int number) {
mItems.get(index).setMessageNumber(number);
}
@Override
public void setHasMessage(int index, boolean hasMessage) {
mItems.get(index).setHasMessage(hasMessage);
}
@Override
public void addTabItemSelectedListener(OnTabItemSelectedListener listener) {
mListeners.add(listener);
}
@Override
public void addSimpleTabItemSelectedListener(SimpleTabItemSelectedListener listener) {
mSimpleListeners.add(listener);
}
@Override
public void setTitle(int index, String title) {
mItems.get(index).setTitle(title);
}
@Override
public void setDefaultDrawable(int index, Drawable drawable) {
mItems.get(index).setDefaultDrawable(drawable);
}
@Override
public void setSelectedDrawable(int index, Drawable drawable) {
mItems.get(index).setSelectedDrawable(drawable);
}
@Override
public int getSelected() {
return mSelected;
}
@Override
public int getItemCount() {
return mItems.size();
}
@Override
public String getItemTitle(int index) {
return mItems.get(index).getTitle();
}
@Override
public boolean removeItem(int index) {
if (index == mSelected || index >= mItems.size() || index < 0) {
return false;
}
if (mSelected > index) {
mSelected--;
}
this.removeViewAt(index);
mItems.remove(index);
return true;
}
@Override
public void addMaterialItem(int index, Drawable defaultDrawable, Drawable selectedDrawable, String title, int selectedColor) {
// nothing
}
@Override
public void addCustomItem(int index,final BaseTabItem item) {
item.setChecked(false);
item.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int index = mItems.indexOf(item);
if (index >= 0) {
setSelect(index);
}
}
});
if (index >= mItems.size()) {
mItems.add(index, item);
this.addView(item);
} else {
mItems.add(index, item);
this.addView(item, index);
}
}
}

View File

@ -0,0 +1,549 @@
package me.majiajie.pagerbottomtabstrip.internal;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.LayoutTransition;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import androidx.core.view.ViewCompat;
import me.majiajie.pagerbottomtabstrip.ItemController;
import me.majiajie.pagerbottomtabstrip.MaterialMode;
import me.majiajie.pagerbottomtabstrip.R;
import me.majiajie.pagerbottomtabstrip.item.BaseTabItem;
import me.majiajie.pagerbottomtabstrip.item.MaterialItemView;
import me.majiajie.pagerbottomtabstrip.listener.OnTabItemSelectedListener;
import me.majiajie.pagerbottomtabstrip.listener.SimpleTabItemSelectedListener;
/**
* 存放 Material Design 风格按钮的水平布局
*/
public class MaterialItemLayout extends ViewGroup implements ItemController {
private final int DEFAULT_SELECTED = 0;
private final int MATERIAL_BOTTOM_NAVIGATION_ACTIVE_ITEM_MAX_WIDTH;
private final int MATERIAL_BOTTOM_NAVIGATION_ITEM_MAX_WIDTH;
private final int MATERIAL_BOTTOM_NAVIGATION_ITEM_MIN_WIDTH;
private final int MATERIAL_BOTTOM_NAVIGATION_ITEM_HEIGHT;
private final List<MaterialItemView> mItems = new ArrayList<>();
private final List<OnTabItemSelectedListener> mListeners = new ArrayList<>();
private final List<SimpleTabItemSelectedListener> mSimpleListeners = new ArrayList<>();
private boolean mItemTintIcon;
private int mItemDefaultColor;
private int[] mTempChildWidths;
private int mItemTotalWidth;
private int mSelected = -1;
private int mOldSelected = -1;
private boolean mHideTitle;
//切换背景颜色时使用
private final int ANIM_TIME = 300;
private Interpolator mInterpolator;
private boolean mChangeBackgroundMode;
private List<Integer> mBackgroundColors;
private List<Oval> mOvals;
private RectF mTempRectF;
private Paint mPaint;
//最后手指抬起的坐标
private float mLastUpX;
private float mLastUpY;
public MaterialItemLayout(Context context) {
this(context, null);
}
public MaterialItemLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MaterialItemLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final Resources res = getResources();
MATERIAL_BOTTOM_NAVIGATION_ACTIVE_ITEM_MAX_WIDTH = res.getDimensionPixelSize(R.dimen.material_bottom_navigation_active_item_max_width);
MATERIAL_BOTTOM_NAVIGATION_ITEM_MAX_WIDTH = res.getDimensionPixelSize(R.dimen.material_bottom_navigation_item_max_width);
MATERIAL_BOTTOM_NAVIGATION_ITEM_MIN_WIDTH = res.getDimensionPixelSize(R.dimen.material_bottom_navigation_item_min_width);
MATERIAL_BOTTOM_NAVIGATION_ITEM_HEIGHT = res.getDimensionPixelSize(R.dimen.material_bottom_navigation_height);
//材料设计规范限制最多只能有5个导航按钮
mTempChildWidths = new int[5];
}
/**
* 初始化方法
*
* @param items 按钮集合
* @param checkedColors 选中颜色的集合
* @param mode {@link MaterialMode}
* @param animateLayoutChanges 是否应用默认的布局动画
* @param doTintIcon item是否需要对图标染色
* @param color item的默认状态颜色
*/
public void initialize(List<MaterialItemView> items, List<Integer> checkedColors, int mode, boolean animateLayoutChanges, boolean doTintIcon, int color) {
if (animateLayoutChanges) {
setLayoutTransition(new LayoutTransition());
}
mItems.clear();
mItems.addAll(items);
mItemTintIcon = doTintIcon;
mItemDefaultColor = color;
//判断是否需要切换背景
if ((mode & MaterialMode.CHANGE_BACKGROUND_COLOR) > 0) {
//初始化一些成员变量
mChangeBackgroundMode = true;
mOvals = new ArrayList<>();
mBackgroundColors = checkedColors;
mInterpolator = new AccelerateDecelerateInterpolator();
mTempRectF = new RectF();
mPaint = new Paint();
//设置默认的背景
setBackgroundColor(mBackgroundColors.get(DEFAULT_SELECTED));
} else {
//设置按钮点击效果
for (int i = 0; i < mItems.size(); i++) {
MaterialItemView v = mItems.get(i);
if (Build.VERSION.SDK_INT >= 21) {
v.setBackground(new RippleDrawable(new ColorStateList(new int[][]{{}}, new int[]{0xFFFFFF & checkedColors.get(i) | 0x56000000}), null, null));
} else {
v.setBackgroundResource(R.drawable.material_item_background);
}
}
}
//判断是否隐藏文字
if ((mode & MaterialMode.HIDE_TEXT) > 0) {
mHideTitle = true;
for (MaterialItemView v : mItems) {
v.setHideTitle(true);
}
}
//添加按钮到布局并注册点击事件
int n = mItems.size();
for (int i = 0; i < n; i++) {
final MaterialItemView tabItem = mItems.get(i);
tabItem.setChecked(false);
this.addView(tabItem);
tabItem.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int index = mItems.indexOf(tabItem);
if (index >= 0) {
setSelect(index, mLastUpX, mLastUpY, true);
}
}
});
}
//默认选中第一项
mSelected = DEFAULT_SELECTED;
mItems.get(DEFAULT_SELECTED).setChecked(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//排除空状态
if (mItems == null || mItems.size() <= 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int count = getChildCount();
final int heightSpec = MeasureSpec.makeMeasureSpec(MATERIAL_BOTTOM_NAVIGATION_ITEM_HEIGHT, MeasureSpec.EXACTLY);
if (mHideTitle) {
final int inactiveCount = count - 1;
final int activeMaxAvailable = width - inactiveCount * MATERIAL_BOTTOM_NAVIGATION_ITEM_MIN_WIDTH;
final int activeWidth = Math.min(activeMaxAvailable, MATERIAL_BOTTOM_NAVIGATION_ACTIVE_ITEM_MAX_WIDTH);
final int inactiveMaxAvailable = inactiveCount == 0 ? 0 : (width - activeWidth) / inactiveCount;
final int inactiveWidth = Math.min(inactiveMaxAvailable, MATERIAL_BOTTOM_NAVIGATION_ITEM_MAX_WIDTH);
for (int i = 0; i < count; i++) {
if (i == mSelected) {
mTempChildWidths[i] = (int) ((activeWidth - inactiveWidth) * mItems.get(mSelected).getAnimValue() + inactiveWidth);
} else if (i == mOldSelected) {
mTempChildWidths[i] = (int) (activeWidth - (activeWidth - inactiveWidth) * mItems.get(mSelected).getAnimValue());
} else {
mTempChildWidths[i] = inactiveWidth;
}
}
} else {
final int maxAvailable = width / (count == 0 ? 1 : count);
final int childWidth = Math.min(maxAvailable, MATERIAL_BOTTOM_NAVIGATION_ACTIVE_ITEM_MAX_WIDTH);
for (int i = 0; i < count; i++) {
mTempChildWidths[i] = childWidth;
}
}
mItemTotalWidth = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.measure(MeasureSpec.makeMeasureSpec(mTempChildWidths[i], MeasureSpec.EXACTLY),
heightSpec);
ViewGroup.LayoutParams params = child.getLayoutParams();
params.width = child.getMeasuredWidth();
mItemTotalWidth += child.getMeasuredWidth();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
final int width = right - left;
final int height = bottom - top;
//只支持topbottom的padding
final int padding_top = getPaddingTop();
final int padding_bottom = getPaddingBottom();
int used = 0;
if (mItemTotalWidth > 0 && mItemTotalWidth < width) {
used = (width - mItemTotalWidth) / 2;
}
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) {
child.layout(width - used - child.getMeasuredWidth(), padding_top, width - used, height - padding_bottom);
} else {
child.layout(used, padding_top, child.getMeasuredWidth() + used, height - padding_bottom);
}
used += child.getMeasuredWidth();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mChangeBackgroundMode) {
int width = getWidth();
int height = getHeight();
Iterator<Oval> iterator = mOvals.iterator();
while (iterator.hasNext()) {
Oval oval = iterator.next();
mPaint.setColor(oval.color);
if (oval.r < oval.maxR) {
mTempRectF.set(oval.getLeft(), oval.getTop(), oval.getRight(), oval.getBottom());
canvas.drawOval(mTempRectF, mPaint);
} else {
this.setBackgroundColor(oval.color);
canvas.drawRect(0, 0, width, height, mPaint);
iterator.remove();
}
invalidate();
}
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
mLastUpX = ev.getX();
mLastUpY = ev.getY();
}
return super.onInterceptTouchEvent(ev);
}
@Override
public CharSequence getAccessibilityClassName() {
return MaterialItemLayout.class.getName();
}
@Override
public void setSelect(int index) {
setSelect(index, true);
}
@Override
public void setSelect(int index, boolean needListener) {
// 不正常的选择项
if (index >= mItems.size() || index < 0) {
return;
}
View v = mItems.get(index);
setSelect(index, v.getX() + v.getWidth() / 2f, v.getY() + v.getHeight() / 2f, needListener);
}
@Override
public void setMessageNumber(int index, int number) {
mItems.get(index).setMessageNumber(number);
}
@Override
public void setHasMessage(int index, boolean hasMessage) {
mItems.get(index).setHasMessage(hasMessage);
}
@Override
public void addTabItemSelectedListener(OnTabItemSelectedListener listener) {
mListeners.add(listener);
}
@Override
public void addSimpleTabItemSelectedListener(SimpleTabItemSelectedListener listener) {
mSimpleListeners.add(listener);
}
@Override
public void setTitle(int index, String title) {
mItems.get(index).setTitle(title);
}
@Override
public void setDefaultDrawable(int index, Drawable drawable) {
mItems.get(index).setDefaultDrawable(drawable);
}
@Override
public void setSelectedDrawable(int index, Drawable drawable) {
mItems.get(index).setSelectedDrawable(drawable);
}
@Override
public int getSelected() {
return mSelected;
}
@Override
public int getItemCount() {
return mItems.size();
}
@Override
public String getItemTitle(int index) {
return mItems.get(index).getTitle();
}
@Override
public boolean removeItem(int index) {
if (index == mSelected || index >= mItems.size() || index < 0) {
return false;
}
if (mSelected > index) {
mSelected--;
}
this.removeViewAt(index);
mItems.remove(index);
if (mChangeBackgroundMode) {
mBackgroundColors.remove(index);
}
return true;
}
@Override
public void addMaterialItem(int index, Drawable defaultDrawable, Drawable selectedDrawable, String title, int selectedColor) {
final MaterialItemView materialItemView = new MaterialItemView(getContext());
materialItemView.initialization(title, defaultDrawable, selectedDrawable, mItemTintIcon, mItemDefaultColor, mChangeBackgroundMode ? Color.WHITE : selectedColor);
materialItemView.setChecked(false);
materialItemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int index = mItems.indexOf(materialItemView);
if (index >= 0) {
setSelect(index);
}
}
});
if (mHideTitle) {
// 隐藏文字
materialItemView.setHideTitle(true);
}
if (mSelected >= index){
mSelected++;
}
if (index >= mItems.size()) {
if (mChangeBackgroundMode) {
mBackgroundColors.add(selectedColor);
}
mItems.add(materialItemView);
this.addView(materialItemView);
} else {
if (mChangeBackgroundMode) {
mBackgroundColors.add(index, selectedColor);
}
mItems.add(index, materialItemView);
this.addView(materialItemView, index);
}
}
@Override
public void addCustomItem(int index, BaseTabItem item) {
// nothing
}
private void setSelect(int index, float x, float y, boolean needListener) {
//重复选择
if (index == mSelected) {
if (needListener) {
for (OnTabItemSelectedListener listener : mListeners) {
listener.onRepeat(mSelected);
}
}
return;
}
//记录前一个选中项和当前选中项
mOldSelected = mSelected;
mSelected = index;
//切换背景颜色
if (mChangeBackgroundMode) {
addOvalColor(mBackgroundColors.get(mSelected), x, y);
}
//前一个选中项必须不小于0才有效
if (mOldSelected >= 0) {
mItems.get(mOldSelected).setChecked(false);
}
mItems.get(mSelected).setChecked(true);
if (needListener) {
//事件回调
for (OnTabItemSelectedListener listener : mListeners) {
listener.onSelected(mSelected, mOldSelected);
}
for (SimpleTabItemSelectedListener listener : mSimpleListeners) {
listener.onSelected(mSelected, mOldSelected);
}
}
}
/**
* 添加一个圆形波纹动画
*
* @param color 颜色
* @param x X座标
* @param y y座标
*/
private void addOvalColor(int color, float x, float y) {
final Oval oval = new Oval(color, 2, x, y);
oval.maxR = getR(x, y);
mOvals.add(oval);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(oval.r, oval.maxR);
valueAnimator.setInterpolator(mInterpolator);
valueAnimator.setDuration(ANIM_TIME);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
oval.r = (float) valueAnimator.getAnimatedValue();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
invalidate();
}
});
valueAnimator.start();
}
/**
* 以矩形内一点为圆心画圆覆盖矩形求这个圆的最小半径
*
* @param x 横坐标
* @param y 纵坐标
* @return 最小半径
*/
private float getR(float x, float y) {
int width = getWidth();
int height = getHeight();
double r1_square = x * x + y * y;
double r2_square = (width - x) * (width - x) + y * y;
double r3_square = (width - x) * (width - x) + (height - y) * (height - y);
double r4_square = x * x + (height - y) * (height - y);
return (float) Math.sqrt(Math.max(Math.max(r1_square, r2_square), Math.max(r3_square, r4_square)));
}
private class Oval {
int color;
float r;
float x;
float y;
float maxR;
Oval(int color, float r, float x, float y) {
this.color = color;
this.r = r;
this.x = x;
this.y = y;
}
float getLeft() {
return x - r;
}
float getTop() {
return y - r;
}
float getRight() {
return x + r;
}
float getBottom() {
return y + r;
}
}
}

View File

@ -0,0 +1,261 @@
package me.majiajie.pagerbottomtabstrip.internal;
import android.animation.LayoutTransition;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
import me.majiajie.pagerbottomtabstrip.ItemController;
import me.majiajie.pagerbottomtabstrip.R;
import me.majiajie.pagerbottomtabstrip.item.BaseTabItem;
import me.majiajie.pagerbottomtabstrip.item.OnlyIconMaterialItemView;
import me.majiajie.pagerbottomtabstrip.listener.OnTabItemSelectedListener;
import me.majiajie.pagerbottomtabstrip.listener.SimpleTabItemSelectedListener;
/**
* 存放 Material Design 风格按钮的垂直布局
*/
public class MaterialItemVerticalLayout extends ViewGroup implements ItemController {
private final int NAVIGATION_ITEM_SIZE;
private final List<BaseTabItem> mItems = new ArrayList<>();
private final List<OnTabItemSelectedListener> mListeners = new ArrayList<>();
private final List<SimpleTabItemSelectedListener> mSimpleListeners = new ArrayList<>();
private int mSelected = -1;
private boolean mItemTintIcon;
private int mItemDefaultColor;
public MaterialItemVerticalLayout(Context context) {
this(context, null);
}
public MaterialItemVerticalLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MaterialItemVerticalLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
NAVIGATION_ITEM_SIZE = getResources().getDimensionPixelSize(R.dimen.material_bottom_navigation_height);
}
public void initialize(List<BaseTabItem> items, boolean animateLayoutChanges, boolean doTintIcon, int color) {
mItems.clear();
mItems.addAll(items);
mItemTintIcon = doTintIcon;
mItemDefaultColor = color;
if (animateLayoutChanges) {
setLayoutTransition(new LayoutTransition());
}
//添加按钮到布局并注册点击事件
int n = mItems.size();
for (int i = 0; i < n; i++) {
final BaseTabItem tabItem = mItems.get(i);
tabItem.setChecked(false);
this.addView(tabItem);
tabItem.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int index = mItems.indexOf(tabItem);
if (index >= 0) {
setSelect(index);
}
}
});
}
//默认选中第一项
mSelected = 0;
mItems.get(0).setChecked(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int n = getChildCount();
final int heightSpec = MeasureSpec.makeMeasureSpec(NAVIGATION_ITEM_SIZE, MeasureSpec.EXACTLY);
final int widthSpec = MeasureSpec.makeMeasureSpec(NAVIGATION_ITEM_SIZE, MeasureSpec.EXACTLY);
for (int i = 0; i < n; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.measure(widthSpec, heightSpec);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
//只支持top的padding
int used = getPaddingTop();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.layout(0, used, child.getMeasuredWidth(), used + child.getMeasuredHeight());
used += child.getMeasuredHeight();
}
}
@Override
public CharSequence getAccessibilityClassName() {
return MaterialItemVerticalLayout.class.getName();
}
@Override
public void setSelect(int index) {
setSelect(index, true);
}
@Override
public void setSelect(int index, boolean needListener) {
//重复选择
if (index == mSelected) {
if (needListener) {
for (OnTabItemSelectedListener listener : mListeners) {
listener.onRepeat(mSelected);
}
}
return;
}
//记录前一个选中项和当前选中项
int oldSelected = mSelected;
mSelected = index;
//前一个选中项必须不小于0才有效
if (oldSelected >= 0) {
mItems.get(oldSelected).setChecked(false);
}
mItems.get(mSelected).setChecked(true);
if (needListener) {
//事件回调
for (OnTabItemSelectedListener listener : mListeners) {
listener.onSelected(mSelected, oldSelected);
}
for (SimpleTabItemSelectedListener listener : mSimpleListeners) {
listener.onSelected(mSelected, oldSelected);
}
}
}
@Override
public void setMessageNumber(int index, int number) {
mItems.get(index).setMessageNumber(number);
}
@Override
public void setHasMessage(int index, boolean hasMessage) {
mItems.get(index).setHasMessage(hasMessage);
}
@Override
public void addTabItemSelectedListener(OnTabItemSelectedListener listener) {
mListeners.add(listener);
}
@Override
public void addSimpleTabItemSelectedListener(SimpleTabItemSelectedListener listener) {
mSimpleListeners.add(listener);
}
@Override
public void setTitle(int index, String title) {
mItems.get(index).setTitle(title);
}
@Override
public void setDefaultDrawable(int index, Drawable drawable) {
mItems.get(index).setDefaultDrawable(drawable);
}
@Override
public void setSelectedDrawable(int index, Drawable drawable) {
mItems.get(index).setSelectedDrawable(drawable);
}
@Override
public int getSelected() {
return mSelected;
}
@Override
public int getItemCount() {
return mItems.size();
}
@Override
public String getItemTitle(int index) {
return mItems.get(index).getTitle();
}
@Override
public boolean removeItem(int index) {
if (index == mSelected || index >= mItems.size() || index < 0) {
return false;
}
if (mSelected > index) {
mSelected--;
}
this.removeViewAt(index);
mItems.remove(index);
return true;
}
@Override
public void addMaterialItem(int index, Drawable defaultDrawable, Drawable selectedDrawable, String title, int selectedColor) {
final OnlyIconMaterialItemView materialItemView = new OnlyIconMaterialItemView(getContext());
materialItemView.initialization(title, defaultDrawable, selectedDrawable, mItemTintIcon, mItemDefaultColor, selectedColor);
materialItemView.setChecked(false);
materialItemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int index = mItems.indexOf(materialItemView);
if (index >= 0) {
setSelect(index);
}
}
});
if (index >= mItems.size()) {
this.addView(materialItemView);
mItems.add(materialItemView);
} else {
this.addView(materialItemView, index);
mItems.add(index, materialItemView);
}
}
@Override
public void addCustomItem(int index, BaseTabItem item) {
// nothing
}
}

View File

@ -0,0 +1,266 @@
package me.majiajie.pagerbottomtabstrip.internal;
import android.animation.LayoutTransition;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import androidx.core.view.ViewCompat;
import java.util.ArrayList;
import java.util.List;
import me.majiajie.pagerbottomtabstrip.ItemController;
import me.majiajie.pagerbottomtabstrip.item.BaseTabItem;
import me.majiajie.pagerbottomtabstrip.listener.OnTabItemSelectedListener;
import me.majiajie.pagerbottomtabstrip.listener.SimpleTabItemSelectedListener;
/**
* 存放自定义项的布局
*/
public class OldCustomItemLayout extends ViewGroup implements ItemController {
private final List<BaseTabItem> mItems = new ArrayList<>();
private final List<OnTabItemSelectedListener> mListeners = new ArrayList<>();
private final List<SimpleTabItemSelectedListener> mSimpleListeners = new ArrayList<>();
private int mSelected = -1;
public OldCustomItemLayout(Context context) {
this(context, null);
}
public OldCustomItemLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public OldCustomItemLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayoutTransition(new LayoutTransition());
}
public void initialize(List<BaseTabItem> items, boolean animateLayoutChanges) {
mItems.clear();
mItems.addAll(items);
if (animateLayoutChanges) {
setLayoutTransition(new LayoutTransition());
}
//添加按钮到布局并注册点击事件
int n = mItems.size();
for (int i = 0; i < n; i++) {
final BaseTabItem tabItem = mItems.get(i);
tabItem.setChecked(false);
this.addView(tabItem);
tabItem.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int index = mItems.indexOf(tabItem);
if (index >= 0) {
setSelect(index);
}
}
});
}
//默认选中第一项
mSelected = 0;
mItems.get(0).setChecked(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int n = getChildCount();
int visableChildCount = 0;
for (int i = 0; i < n; i++) {
if (getChildAt(i).getVisibility() != GONE) {
visableChildCount++;
}
}
if (visableChildCount == 0) {
return;
}
final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec) / visableChildCount, MeasureSpec.EXACTLY);
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(0, MeasureSpec.getSize(heightMeasureSpec) - getPaddingBottom() - getPaddingTop()), MeasureSpec.EXACTLY);
for (int i = 0; i < n; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
final int width = right - left;
final int height = bottom - top;
//只支持topbottom的padding
final int padding_top = getPaddingTop();
final int padding_bottom = getPaddingBottom();
int used = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) {
child.layout(width - used - child.getMeasuredWidth(), padding_top, width - used, height - padding_bottom);
} else {
child.layout(used, padding_top, child.getMeasuredWidth() + used, height - padding_bottom);
}
used += child.getMeasuredWidth();
}
}
@Override
public CharSequence getAccessibilityClassName() {
return CustomItemLayout.class.getName();
}
@Override
public void setSelect(int index) {
setSelect(index, true);
}
@Override
public void setSelect(int index, boolean needListener) {
//重复选择
if (index == mSelected) {
if (needListener) {
for (OnTabItemSelectedListener listener : mListeners) {
mItems.get(mSelected).onRepeat();
listener.onRepeat(mSelected);
}
}
return;
}
//记录前一个选中项和当前选中项
int oldSelected = mSelected;
mSelected = index;
//前一个选中项必须不小于0才有效
if (oldSelected >= 0) {
mItems.get(oldSelected).setChecked(false);
}
mItems.get(mSelected).setChecked(true);
if (needListener) {
//事件回调
for (OnTabItemSelectedListener listener : mListeners) {
listener.onSelected(mSelected, oldSelected);
}
for (SimpleTabItemSelectedListener listener : mSimpleListeners) {
listener.onSelected(mSelected, oldSelected);
}
}
}
@Override
public void setMessageNumber(int index, int number) {
mItems.get(index).setMessageNumber(number);
}
@Override
public void setHasMessage(int index, boolean hasMessage) {
mItems.get(index).setHasMessage(hasMessage);
}
@Override
public void addTabItemSelectedListener(OnTabItemSelectedListener listener) {
mListeners.add(listener);
}
@Override
public void addSimpleTabItemSelectedListener(SimpleTabItemSelectedListener listener) {
mSimpleListeners.add(listener);
}
@Override
public void setTitle(int index, String title) {
mItems.get(index).setTitle(title);
}
@Override
public void setDefaultDrawable(int index, Drawable drawable) {
mItems.get(index).setDefaultDrawable(drawable);
}
@Override
public void setSelectedDrawable(int index, Drawable drawable) {
mItems.get(index).setSelectedDrawable(drawable);
}
@Override
public int getSelected() {
return mSelected;
}
@Override
public int getItemCount() {
return mItems.size();
}
@Override
public String getItemTitle(int index) {
return mItems.get(index).getTitle();
}
@Override
public boolean removeItem(int index) {
if (index == mSelected || index >= mItems.size() || index < 0) {
return false;
}
if (mSelected > index) {
mSelected--;
}
this.removeViewAt(index);
mItems.remove(index);
return true;
}
@Override
public void addMaterialItem(int index, Drawable defaultDrawable, Drawable selectedDrawable, String title, int selectedColor) {
// nothing
}
@Override
public void addCustomItem(int index, final BaseTabItem item) {
item.setChecked(false);
item.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int index = mItems.indexOf(item);
if (index >= 0) {
setSelect(index);
}
}
});
if (index >= mItems.size()) {
mItems.add(index, item);
this.addView(item);
} else {
mItems.add(index, item);
this.addView(item, index);
}
}
}

View File

@ -0,0 +1,102 @@
package me.majiajie.pagerbottomtabstrip.internal;
import android.content.Context;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import java.util.Locale;
import me.majiajie.pagerbottomtabstrip.R;
public class RoundMessageView extends FrameLayout {
private final View mOval;
private final TextView mMessages;
private int mMessageNumber;
private boolean mHasMessage;
public RoundMessageView(Context context) {
this(context, null);
}
public RoundMessageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundMessageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater.from(context).inflate(R.layout.round_message_view, this, true);
mOval = findViewById(R.id.oval);
mMessages = findViewById(R.id.msg);
mMessages.setTypeface(Typeface.DEFAULT_BOLD);
mMessages.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 10);
}
public void setMessageNumber(int number) {
mMessageNumber = number;
if (mMessageNumber > 0) {
mOval.setVisibility(View.INVISIBLE);
mMessages.setVisibility(View.VISIBLE);
if (mMessageNumber < 10) {
mMessages.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 10);
} else {
mMessages.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 8);
}
if (mMessageNumber <= 99) {
mMessages.setText(String.format(Locale.ENGLISH, "%d", mMessageNumber));
} else {
mMessages.setText(String.format(Locale.ENGLISH, "%d+", 99));
}
} else {
mMessages.setVisibility(View.INVISIBLE);
if (mHasMessage) {
mOval.setVisibility(View.VISIBLE);
}
}
}
public void setHasMessage(boolean hasMessage) {
mHasMessage = hasMessage;
if (hasMessage) {
mOval.setVisibility(mMessageNumber > 0 ? View.INVISIBLE : View.VISIBLE);
} else {
mOval.setVisibility(View.INVISIBLE);
}
}
public void tintMessageBackground(@ColorInt int color) {
Drawable drawable = Utils.tinting(ContextCompat.getDrawable(getContext(), R.drawable.round), color);
ViewCompat.setBackground(mOval, drawable);
ViewCompat.setBackground(mMessages, drawable);
}
public void setMessageNumberColor(@ColorInt int color) {
mMessages.setTextColor(color);
}
public int getMessageNumber() {
return mMessageNumber;
}
public boolean hasMessage() {
return mHasMessage;
}
}

View File

@ -0,0 +1,61 @@
package me.majiajie.pagerbottomtabstrip.internal;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.TypedValue;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
public class Utils {
/**
* Drawable 染色
*
* @param drawable 染色对象
* @param color 颜色
* @return 染色后的资源
*/
public static Drawable tinting(Drawable drawable, int color) {
final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
wrappedDrawable.mutate();
DrawableCompat.setTint(wrappedDrawable, color);
return wrappedDrawable;
}
public static Drawable newDrawable(Drawable drawable) {
Drawable.ConstantState constantState = drawable.getConstantState();
return constantState != null ? constantState.newDrawable() : drawable;
}
/**
* 获取colorPrimary的颜色,需要V7包的支持
*
* @param context 上下文
* @return 0xAARRGGBB
*/
public static int getColorPrimary(Context context) {
Resources res = context.getResources();
int attrRes = res.getIdentifier("colorPrimary", "attr", context.getPackageName());
if (attrRes == 0) {
return 0xFF009688;
}
return ContextCompat.getColor(context, getResourceId(context, attrRes));
}
/**
* 获取自定义属性的资源ID
*
* @param context 上下文
* @param attrRes 自定义属性
* @return resourceId
*/
private static int getResourceId(Context context, int attrRes) {
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(attrRes, typedValue, true);
return typedValue.resourceId;
}
}

View File

@ -0,0 +1,71 @@
package me.majiajie.pagerbottomtabstrip.item;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* 所有自定义Item都必须继承此类
*/
public abstract class BaseTabItem extends FrameLayout {
public BaseTabItem(@NonNull Context context) {
super(context);
}
public BaseTabItem(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public BaseTabItem(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 设置选中状态
*/
abstract public void setChecked(boolean checked);
/**
* 设置消息数字注意一般数字需要大于0才会显示
*/
abstract public void setMessageNumber(int number);
/**
* 设置是否显示无数字的小圆点注意如果消息数字不为0优先显示带数字的圆
*/
abstract public void setHasMessage(boolean hasMessage);
/**
* 设置标题
*/
abstract public void setTitle(String title);
/**
* 设置未选中状态下的图标
*/
abstract public void setDefaultDrawable(Drawable drawable);
/**
* 设置选中状态下的图标
*/
abstract public void setSelectedDrawable(Drawable drawable);
/**
* 获取标题文字
*/
abstract public String getTitle();
/**
* 已选中的状态下再次点击时触发
*/
public void onRepeat() {}
}

View File

@ -0,0 +1,253 @@
package me.majiajie.pagerbottomtabstrip.item;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import me.majiajie.pagerbottomtabstrip.R;
import me.majiajie.pagerbottomtabstrip.internal.RoundMessageView;
import me.majiajie.pagerbottomtabstrip.internal.Utils;
/**
* 材料设计风格项
*/
public class MaterialItemView extends BaseTabItem {
private final RoundMessageView mMessages;
private final TextView mLabel;
private final ImageView mIcon;
private Drawable mDefaultDrawable;
private Drawable mCheckedDrawable;
private int mDefaultColor;
private int mCheckedColor;
private final float mTranslation;
private final float mTranslationHideTitle;
private final int mTopMargin;
private final int mTopMarginHideTitle;
private boolean mHideTitle;
private boolean mChecked;
private ValueAnimator mAnimator;
private float mAnimatorValue = 1f;
private boolean mIsMeasured = false;
private boolean mTintIcon = true;
public MaterialItemView(@NonNull Context context) {
this(context, null);
}
public MaterialItemView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MaterialItemView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
final float scale = context.getResources().getDisplayMetrics().density;
mTranslation = scale * 2;
mTranslationHideTitle = scale * 10;
mTopMargin = (int) (scale * 8);
mTopMarginHideTitle = (int) (scale * 16);
LayoutInflater.from(context).inflate(R.layout.item_material, this, true);
mIcon = findViewById(R.id.icon);
mLabel = findViewById(R.id.label);
mMessages = findViewById(R.id.messages);
}
@Override
public CharSequence getAccessibilityClassName() {
return MaterialItemView.class.getName();
}
public void initialization(String title, Drawable drawable, Drawable checkedDrawable, boolean tintIcon, int color, int checkedColor) {
mTintIcon = tintIcon;
mDefaultColor = color;
mCheckedColor = checkedColor;
if (mTintIcon) {
mDefaultDrawable = Utils.tinting(drawable, mDefaultColor);
mCheckedDrawable = Utils.tinting(checkedDrawable, mCheckedColor);
} else {
mDefaultDrawable = drawable;
mCheckedDrawable = checkedDrawable;
}
mLabel.setText(title);
mLabel.setTextColor(color);
mIcon.setImageDrawable(mDefaultDrawable);
mAnimator = ValueAnimator.ofFloat(1f);
mAnimator.setDuration(115L);
mAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAnimatorValue = (float) animation.getAnimatedValue();
if (mHideTitle) {
mIcon.setTranslationY(-mTranslationHideTitle * mAnimatorValue);
} else {
mIcon.setTranslationY(-mTranslation * mAnimatorValue);
}
mLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f + mAnimatorValue * 2f);
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mIsMeasured = true;
}
@Override
public void setChecked(boolean checked) {
if (mChecked == checked) {
return;
}
mChecked = checked;
if (mHideTitle) {
mLabel.setVisibility(mChecked ? View.VISIBLE : View.INVISIBLE);
}
if (mIsMeasured) {
// 切换动画
if (mChecked) {
mAnimator.start();
} else {
mAnimator.reverse();
}
} else if (mChecked) { // 布局还未测量时选中直接转换到选中的最终状态
if (mHideTitle) {
mIcon.setTranslationY(-mTranslationHideTitle);
} else {
mIcon.setTranslationY(-mTranslation);
}
mLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f);
} else { // 布局还未测量并且未选中保持未选中状态
mIcon.setTranslationY(0);
mLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f);
}
// 切换颜色
if (mChecked) {
mIcon.setImageDrawable(mCheckedDrawable);
mLabel.setTextColor(mCheckedColor);
} else {
mIcon.setImageDrawable(mDefaultDrawable);
mLabel.setTextColor(mDefaultColor);
}
}
@Override
public void setMessageNumber(int number) {
mMessages.setVisibility(View.VISIBLE);
mMessages.setMessageNumber(number);
}
@Override
public void setHasMessage(boolean hasMessage) {
mMessages.setVisibility(View.VISIBLE);
mMessages.setHasMessage(hasMessage);
}
@Override
public void setTitle(String title) {
mLabel.setText(title);
}
@Override
public void setDefaultDrawable(Drawable drawable) {
if (mTintIcon) {
mDefaultDrawable = Utils.tinting(drawable, mDefaultColor);
} else {
mDefaultDrawable = drawable;
}
if (!mChecked) {
mIcon.setImageDrawable(mDefaultDrawable);
}
}
@Override
public void setSelectedDrawable(Drawable drawable) {
if (mTintIcon) {
mCheckedDrawable = Utils.tinting(drawable, mCheckedColor);
} else {
mCheckedDrawable = drawable;
}
if (mChecked) {
mIcon.setImageDrawable(mCheckedDrawable);
}
}
@Override
public String getTitle() {
return mLabel.getText().toString();
}
/**
* 获取动画运行值[0,1]
*/
public float getAnimValue() {
return mAnimatorValue;
}
/**
* 设置是否隐藏文字
*/
public void setHideTitle(boolean hideTitle) {
mHideTitle = hideTitle;
LayoutParams iconParams = (LayoutParams) mIcon.getLayoutParams();
if (mHideTitle) {
iconParams.topMargin = mTopMarginHideTitle;
} else {
iconParams.topMargin = mTopMargin;
}
mLabel.setVisibility(mChecked ? View.VISIBLE : View.INVISIBLE);
mIcon.setLayoutParams(iconParams);
}
/**
* 设置消息圆形的颜色
*/
public void setMessageBackgroundColor(@ColorInt int color) {
mMessages.tintMessageBackground(color);
}
/**
* 设置消息数据的颜色
*/
public void setMessageNumberColor(@ColorInt int color) {
mMessages.setMessageNumberColor(color);
}
}

View File

@ -0,0 +1,133 @@
package me.majiajie.pagerbottomtabstrip.item;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.core.content.ContextCompat;
import me.majiajie.pagerbottomtabstrip.R;
import me.majiajie.pagerbottomtabstrip.internal.RoundMessageView;
public class NormalItemView extends BaseTabItem {
private ImageView mIcon;
private final TextView mTitle;
private final RoundMessageView mMessages;
private Drawable mDefaultDrawable;
private Drawable mCheckedDrawable;
private int mDefaultTextColor = 0x56000000;
private int mCheckedTextColor = 0x56000000;
private boolean mChecked;
public NormalItemView(Context context) {
this(context, null);
}
public NormalItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NormalItemView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// LayoutInflater.from(context).inflate(R.layout.item_normal, this, true);
LayoutInflater.from(context).inflate(R.layout.item_normal_new, this, true);
mIcon = findViewById(R.id.icon);
mTitle = findViewById(R.id.title);
mMessages = findViewById(R.id.messages);
}
@Override
public CharSequence getAccessibilityClassName() {
return NormalItemView.class.getName();
}
/**
* 方便初始化的方法
*
* @param drawableRes 默认状态的图标
* @param checkedDrawableRes 选中状态的图标
* @param title 标题
*/
public void initialize(@DrawableRes int drawableRes, @DrawableRes int checkedDrawableRes, String title) {
mDefaultDrawable = ContextCompat.getDrawable(getContext(), drawableRes);
mCheckedDrawable = ContextCompat.getDrawable(getContext(), checkedDrawableRes);
mTitle.setText(title);
}
public void initialize(@DrawableRes int drawableRes, @DrawableRes int checkedDrawableRes, String title,int textsize) {
mDefaultDrawable = ContextCompat.getDrawable(getContext(), drawableRes);
mCheckedDrawable = ContextCompat.getDrawable(getContext(), checkedDrawableRes);
mTitle.setText(title);
mTitle.setTextSize(textsize);
}
public void initialize(String title,int textsize) {
mIcon.setVisibility(GONE);
mTitle.setText(title);
mTitle.setTextSize(textsize);
}
@Override
public void setChecked(boolean checked) {
if (checked) {
mIcon.setImageDrawable(mCheckedDrawable);
mTitle.setTextColor(mCheckedTextColor);
} else {
mIcon.setImageDrawable(mDefaultDrawable);
mTitle.setTextColor(mDefaultTextColor);
}
mChecked = checked;
}
@Override
public void setMessageNumber(int number) {
mMessages.setMessageNumber(number);
}
@Override
public void setHasMessage(boolean hasMessage) {
mMessages.setHasMessage(hasMessage);
}
@Override
public void setTitle(String title) {
mTitle.setText(title);
}
@Override
public void setDefaultDrawable(Drawable drawable) {
mDefaultDrawable = drawable;
if (!mChecked) {
mIcon.setImageDrawable(drawable);
}
}
@Override
public void setSelectedDrawable(Drawable drawable) {
mCheckedDrawable = drawable;
if (mChecked) {
mIcon.setImageDrawable(drawable);
}
}
@Override
public String getTitle() {
return mTitle.getText().toString();
}
public void setTextDefaultColor(@ColorInt int color) {
mDefaultTextColor = color;
}
public void setTextCheckedColor(@ColorInt int color) {
mCheckedTextColor = color;
}
}

View File

@ -0,0 +1,166 @@
package me.majiajie.pagerbottomtabstrip.item;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import me.majiajie.pagerbottomtabstrip.R;
import me.majiajie.pagerbottomtabstrip.internal.RoundMessageView;
import me.majiajie.pagerbottomtabstrip.internal.Utils;
/**
* 只有图标的材料设计项(用于垂直布局)
*/
public class OnlyIconMaterialItemView extends BaseTabItem {
private final RoundMessageView mMessages;
private final ImageView mIcon;
private Drawable mDefaultDrawable;
private Drawable mCheckedDrawable;
private int mDefaultColor;
private int mCheckedColor;
private String mTitle;
private boolean mChecked;
private boolean mTintIcon = true;
public OnlyIconMaterialItemView(@NonNull Context context) {
this(context, null);
}
public OnlyIconMaterialItemView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public OnlyIconMaterialItemView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater.from(context).inflate(R.layout.item_material_only_icon, this, true);
mIcon = findViewById(R.id.icon);
mMessages = findViewById(R.id.messages);
}
@Override
public CharSequence getAccessibilityClassName() {
return OnlyIconMaterialItemView.class.getName();
}
public void initialization(String title, Drawable drawable, Drawable checkedDrawable, boolean tintIcon, int color, int checkedColor) {
mTitle = title;
mDefaultColor = color;
mCheckedColor = checkedColor;
mTintIcon = tintIcon;
if (mTintIcon) {
mDefaultDrawable = Utils.tinting(drawable, mDefaultColor);
mCheckedDrawable = Utils.tinting(checkedDrawable, mCheckedColor);
} else {
mDefaultDrawable = drawable;
mCheckedDrawable = checkedDrawable;
}
mIcon.setImageDrawable(mDefaultDrawable);
if (Build.VERSION.SDK_INT >= 21) {
setBackground(new RippleDrawable(new ColorStateList(new int[][]{{}}, new int[]{0xFFFFFF & checkedColor | 0x56000000}), null, null));
} else {
setBackgroundResource(R.drawable.material_item_background);
}
}
@Override
public void setChecked(boolean checked) {
if (mChecked == checked) {
return;
}
mChecked = checked;
// 切换颜色
if (mChecked) {
mIcon.setImageDrawable(mCheckedDrawable);
} else {
mIcon.setImageDrawable(mDefaultDrawable);
}
}
@Override
public void setMessageNumber(int number) {
mMessages.setVisibility(View.VISIBLE);
mMessages.setMessageNumber(number);
}
@Override
public void setHasMessage(boolean hasMessage) {
mMessages.setVisibility(View.VISIBLE);
mMessages.setHasMessage(hasMessage);
}
@Override
public void setTitle(String title) {
// no title
}
@Override
public void setDefaultDrawable(Drawable drawable) {
if (mTintIcon) {
mDefaultDrawable = Utils.tinting(drawable, mDefaultColor);
} else {
mDefaultDrawable = drawable;
}
if (!mChecked) {
mIcon.setImageDrawable(mDefaultDrawable);
}
}
@Override
public void setSelectedDrawable(Drawable drawable) {
if (mTintIcon) {
mCheckedDrawable = Utils.tinting(drawable, mCheckedColor);
} else {
mCheckedDrawable = drawable;
}
if (mChecked) {
mIcon.setImageDrawable(mCheckedDrawable);
}
}
@Override
public String getTitle() {
return mTitle;
}
/**
* 设置消息圆形的颜色
*/
public void setMessageBackgroundColor(@ColorInt int color) {
mMessages.tintMessageBackground(color);
}
/**
* 设置消息数据的颜色
*/
public void setMessageNumberColor(@ColorInt int color) {
mMessages.setMessageNumberColor(color);
}
}

View File

@ -0,0 +1,22 @@
package me.majiajie.pagerbottomtabstrip.listener;
/**
* 导航栏按钮选中事件
*/
public interface OnTabItemSelectedListener {
/**
* 选中导航栏的某一项
*
* @param index 索引导航按钮按添加顺序排序
* @param old 前一个选中项如果没有就等于-1
*/
void onSelected(int index, int old);
/**
* 重复选中
*
* @param index 索引导航按钮按添加顺序排序
*/
void onRepeat(int index);
}

View File

@ -0,0 +1,16 @@
package me.majiajie.pagerbottomtabstrip.listener;
/**
* 导航栏按钮选中事件
*/
public interface SimpleTabItemSelectedListener {
/**
* 选中导航栏的某一项
*
* @param index 索引导航按钮按添加顺序排序
* @param old 前一个选中项如果没有就等于-1
*/
void onSelected(int index, int old);
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#33333333"/>
</shape>
</item>
<item android:drawable="@android:color/transparent"/>
</selector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<!-- <solid android:color="@android:color/holo_red_light"/>-->
<solid android:color="#EB322D"/>
</shape>

View File

@ -0,0 +1,39 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="@dimen/material_bottom_navigation_margin"
android:layout_marginTop="@dimen/material_bottom_navigation_margin" />
<me.majiajie.pagerbottomtabstrip.internal.BaselineLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:clipToPadding="false"
android:paddingBottom="10dp"
>
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:textSize="@dimen/material_bottom_navigation_text_size"
tools:text="123" />
</me.majiajie.pagerbottomtabstrip.internal.BaselineLayout>
<me.majiajie.pagerbottomtabstrip.internal.RoundMessageView
android:id="@+id/messages"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="3dp"
android:translationX="14dp"
android:visibility="gone" />
</merge>

View File

@ -0,0 +1,20 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center" />
<me.majiajie.pagerbottomtabstrip.internal.RoundMessageView
android:id="@+id/messages"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="11dp"
android:translationX="14dp"
android:visibility="gone" />
</merge>

View File

@ -0,0 +1,41 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp" />
<me.majiajie.pagerbottomtabstrip.internal.BaselineLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:clipToPadding="false"
android:paddingBottom="10dp">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:textSize="12sp"
tools:text="123" />
</me.majiajie.pagerbottomtabstrip.internal.BaselineLayout>
<me.majiajie.pagerbottomtabstrip.internal.RoundMessageView
android:id="@+id/messages"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="3dp"
android:translationX="14dp" />
</merge>

View File

@ -0,0 +1,45 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="22dp"
/>
<!-- <me.majiajie.pagerbottomtabstrip.internal.BaselineLayout-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_gravity="bottom|center_horizontal"-->
<!-- android:clipToPadding="false"-->
<!-- android:paddingBottom="24dp">-->
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:maxLines="1"
android:textSize="12sp"
android:paddingBottom="14dp"
tools:text="123" />
<!-- </me.majiajie.pagerbottomtabstrip.internal.BaselineLayout>-->
<me.majiajie.pagerbottomtabstrip.internal.RoundMessageView
android:id="@+id/messages"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="14dp"
android:translationX="16dp"
android:translationY="-1dp"/>
</merge>

View File

@ -0,0 +1,39 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/icon"
android:layout_width="33dp"
android:layout_height="33dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="4dp"
/>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:maxLines="1"
android:textSize="14sp"
android:paddingBottom="4dp"
tools:text="123" />
<!-- </me.majiajie.pagerbottomtabstrip.internal.BaselineLayout>-->
<me.majiajie.pagerbottomtabstrip.internal.RoundMessageView
android:id="@+id/messages"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="14dp"
android:translationX="16dp"
android:translationY="-1dp"/>
</merge>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<View
android:id="@+id/oval"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="2dp"
android:background="@drawable/round"
android:visibility="invisible" />
<TextView
android:id="@+id/msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/round"
android:gravity="center"
android:minHeight="20dp"
android:minWidth="20dp"
android:singleLine="true"
android:textColor="@android:color/white"
android:visibility="invisible" />
</merge>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">PagerBottomTabStrip</string>
</resources>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="material_bottom_navigation_active_item_max_width">168dp</dimen>
<dimen name="material_bottom_navigation_active_text_size">14sp</dimen>
<dimen name="material_bottom_navigation_elevation">8dp</dimen>
<dimen name="material_bottom_navigation_height">56dp</dimen>
<dimen name="material_bottom_navigation_item_max_width">96dp</dimen>
<dimen name="material_bottom_navigation_item_min_width">56dp</dimen>
<dimen name="material_bottom_navigation_margin">8dp</dimen>
<dimen name="material_bottom_navigation_shadow_height">1dp</dimen>
<dimen name="material_bottom_navigation_text_size">12sp</dimen>
<declare-styleable name="PageNavigationView">
<attr name="NavigationPaddingTop" format="dimension"/>
<attr name="NavigationPaddingBottom" format="dimension"/>
</declare-styleable>
</resources>

View File

@ -0,0 +1,22 @@
ext {
libraryName = 'PagerBottomTabStrip'
libraryDescription = 'An navigation bar for Android'
publishedGroupId = 'me.majiajie'
artifact = 'pager-bottom-tab-strip'
siteUrl = 'https://github.com/tyzlmjj/PagerBottomTabStrip'
scmUrl = 'github.com/tyzlmjj/PagerBottomTabStrip.git'
libraryVersion = rootProject.ext.versionName
developerId = 'tyzlmjj'
developerName = 'Ma JiaJie'
developerEmail = 'tyzl931019@gmail.com'
licenseName = 'The Apache Software License, Version 2.0'
licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
apply from: 'https://raw.githubusercontent.com/tyzlmjj/Gradle/master/MavenCentral/publish_module.gradle'

1
selector/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

22
selector/build.gradle Normal file
View File

@ -0,0 +1,22 @@
apply plugin: 'com.android.library'
android {
namespace 'chihane.jdaddressselector'
compileSdkVersion 29
defaultConfig {
minSdkVersion 21
targetSdkVersion 29
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.0.0'
}

17
selector/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Users\Administrator\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# 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 *;
#}

View File

@ -0,0 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chihane.jdaddressselector">
<application
android:allowBackup="true"
android:supportsRtl="true">
</application>
</manifest>

View File

@ -0,0 +1,56 @@
package chihane.jdaddressselector;
import android.app.Dialog;
import android.content.Context;
import android.view.Gravity;
import android.view.Window;
import android.view.WindowManager;
public class BottomDialog extends Dialog {
private Selector selector;
public BottomDialog(Context context) {
super(context, R.style.bottom_dialog);
}
public BottomDialog(Context context, int themeResId) {
super(context, themeResId);
}
public BottomDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
super(context, cancelable, cancelListener);
}
public void init(Context context,Selector selector) {
this.selector = selector;
setContentView(selector.getView());
Window window = getWindow();
WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = dip2px(context, 256);
window.setAttributes(params);
window.setGravity(Gravity.BOTTOM);
}
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
public void setOnAddressSelectedListener(SelectedListener listener) {
this.selector.setSelectedListener(listener);
}
public static BottomDialog show(Context context) {
return show(context, null);
}
public static BottomDialog show(Context context, SelectedListener listener) {
BottomDialog dialog = new BottomDialog(context, R.style.bottom_dialog);
dialog.selector.setSelectedListener(listener);
dialog.show();
return dialog;
}
}

View File

@ -0,0 +1,16 @@
package chihane.jdaddressselector;
import java.util.List;
/**
* Created by dun on 17/2/9.
*/
public interface DataProvider {
void provideData(int currentDeep,int preId,DataReceiver receiver);
interface DataReceiver {
void send(List<ISelectAble> data);
}
}

View File

@ -0,0 +1,25 @@
package chihane.jdaddressselector;
/**
* Created by dun on 17/2/9.
*/
public interface ISelectAble {
/**
* 显示在栏目上的名字
* */
public String getName();
/**
* 用户设定的id根据这个id可以获取级栏目或者指定为最终栏目的id
* */
public int getId();
public String getUuid();
/**
* 自定义类型对象
* */
public String county_name();
public String city_name();
public String prov_name();;
}

View File

@ -0,0 +1,71 @@
package chihane.jdaddressselector;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
/**
* Created by dun on 17/2/9.
*/
public class SelectAdapter extends BaseAdapter{
List<ISelectAble> datas;
int selectedIndex = Selector.INDEX_INVALID;
public SelectAdapter(List<ISelectAble> datas) {
this.datas = datas;
}
public void setSelectedIndex(int selectedIndex) {
this.selectedIndex = selectedIndex;
}
@Override
public int getCount() {
return datas.size();
}
@Override
public Object getItem(int position) {
return datas.get(position);
}
@Override
public long getItemId(int position) {
return datas.get(position).getId();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Holder holder;
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_area, parent, false);
holder = new Holder();
holder.textView = (TextView) convertView.findViewById(R.id.textView);
holder.imageViewCheckMark = (ImageView) convertView.findViewById(R.id.imageViewCheckMark);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
ISelectAble item = (ISelectAble) getItem(position);
holder.textView.setText(item.getName());
boolean checked = selectedIndex != Selector.INDEX_INVALID && datas.get(selectedIndex).getId() == item.getId();
holder.textView.setEnabled(!checked);
holder.imageViewCheckMark.setVisibility(checked ? View.VISIBLE : View.GONE);
return convertView;
}
class Holder {
TextView textView;
ImageView imageViewCheckMark;
}
}

View File

@ -0,0 +1,11 @@
package chihane.jdaddressselector;
import java.util.ArrayList;
public interface SelectedListener {
/**
* 回调接口根据选择深度按顺序返回选择的对象
* */
void onAddressSelected(ArrayList<ISelectAble> selectAbles);
}

View File

@ -0,0 +1,259 @@
package chihane.jdaddressselector;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import java.util.ArrayList;
import java.util.List;
public class Selector implements AdapterView.OnItemClickListener {
public static final int INDEX_INVALID = -1;
private final Context context;
private SelectedListener listener;
private View view;
private View indicator;
private LinearLayout ll_tabLayout;
private ProgressBar progressBar;
private ListView listView;
private int tabIndex = 0;
/* 每个tab的adapter */ List<List<ISelectAble>> allDatas = new ArrayList<>();
private SelectAdapter[] adapters;
/*选择的深度*/
private int selectDeep;
private int[] selectedIndex;
DataProvider dataProvider;
public void setDataProvider(DataProvider dataProvider) {
this.dataProvider = dataProvider;
getNextData(0);
}
public Selector(Context context, int deep) {
this.context = context;
this.allDatas = new ArrayList<>(deep);
selectedIndex = new int[deep];
this.selectDeep = deep;
for (int i = 0; i < deep; i++) {
allDatas.add(new ArrayList<ISelectAble>());
}
initAdapters();
initViews();
}
private void initAdapters() {
adapters = new SelectAdapter[allDatas.size()];
for (int i = 0; i < selectDeep; i++) {
adapters[i] = new SelectAdapter(allDatas.get(i));
}
}
private TextView[] tabs;
private void initViews() {
view = LayoutInflater.from(context).inflate(R.layout.address_selector, null);
this.progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
this.listView = (ListView) view.findViewById(R.id.listView);
this.indicator = view.findViewById(R.id.indicator);
this.ll_tabLayout = (LinearLayout) view.findViewById(R.id.layout_tab);
tabs = new TextView[allDatas.size()];
for (int i = 0; i < allDatas.size(); i++) {
//动态新增TextView
TextView textView = (TextView) LayoutInflater.from(context).inflate(R.layout.simple_text_view, ll_tabLayout, false);
ll_tabLayout.addView(textView);
//绑定TextView的点击事件
final int finalI = i;
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//设置tab 下标
tabIndex = finalI + 1;
//更新adapter
listView.setAdapter(adapters[finalI]);
//设置选择位置
if (selectedIndex[finalI] != INDEX_INVALID) {
listView.setSelection(selectedIndex[finalI]);
}
updateTabsVisibility(tabIndex-1);
updateIndicator(tabIndex - 1);
}
});
tabs[i] = textView;
}
this.listView.setOnItemClickListener(this);
updateIndicator(tabIndex);
}
public View getView() {
return view;
}
/**
* 指示器动画
*/
private void updateIndicator(final int tabIndex) {
view.post(new Runnable() {
@Override
public void run() {
buildIndicatorAnimatorTowards(tabs[tabIndex]).start();
}
});
}
private AnimatorSet buildIndicatorAnimatorTowards(TextView tab) {
ObjectAnimator xAnimator = ObjectAnimator.ofFloat(indicator, "X", indicator.getX(), tab.getX());
final ViewGroup.LayoutParams params = indicator.getLayoutParams();
ValueAnimator widthAnimator = ValueAnimator.ofInt(params.width, tab.getMeasuredWidth());
widthAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
params.width = (int) animation.getAnimatedValue();
indicator.setLayoutParams(params);
}
});
AnimatorSet set = new AnimatorSet();
set.setInterpolator(new FastOutSlowInInterpolator());
set.playTogether(xAnimator, widthAnimator);
return set;
}
private void updateTabsVisibility(int index) {
for (int i = 0; i < tabs.length; i++) {
TextView tv = tabs[i];
tv.setVisibility(allDatas.get(i).size() != 0 ? View.VISIBLE : View.GONE);
tv.setEnabled(index != i);
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
this.selectedIndex[tabIndex - 1] = position;
ISelectAble selectAble = allDatas.get(tabIndex - 1).get(position);
tabs[tabIndex-1].setText(selectAble.getName());
for (int i = tabIndex; i < this.allDatas.size(); i++) {
tabs[i].setText("请选择");
allDatas.get(i).clear();
adapters[i].setSelectedIndex(-1);
adapters[i].notifyDataSetChanged();
this.selectedIndex[i] = INDEX_INVALID;
}
this.adapters[tabIndex - 1].setSelectedIndex(position);
this.adapters[tabIndex - 1].notifyDataSetChanged();
// if(tabIndex == selectDeep-1)
// {
// callbackInternalno();
// }
if (tabIndex == selectDeep) {
callbackInternal();
return;
}
updateTabsVisibility(tabIndex -1);
updateIndicator(tabIndex);
getNextData(selectAble.getId());
}
/**
* 根据当前集合选择的id向用户获取下一级子集的数据
*/
private void getNextData(int preId) {
if (dataProvider == null) {
return;
}
progressBar.setVisibility(View.VISIBLE);
dataProvider.provideData(tabIndex, preId, new DataProvider.DataReceiver() {
@Override
public void send(List<ISelectAble> data) {
if (data.size() > 0) {
//更新当前tab下标
allDatas.get(tabIndex).clear();
allDatas.get(tabIndex).addAll(data);
adapters[tabIndex].notifyDataSetChanged();
listView.setAdapter(adapters[tabIndex]);
} else {
//次级没有内容直接回调
callbackInternal();
}
updateTabsVisibility(tabIndex);
updateProgressVisibility();
updateIndicator(tabIndex);
tabIndex = tabIndex + 1 >= selectDeep ? selectDeep : tabIndex + 1;
}
});
}
private void callbackInternal() {
if (listener != null) {
ArrayList<ISelectAble> result = new ArrayList<>(allDatas.size());
for (int i = 0; i < selectDeep; i++) {
ISelectAble resultBean = allDatas.get(i) == null
|| selectedIndex[i] == INDEX_INVALID ? null : allDatas.get(i).get(selectedIndex[i]);
result.add(resultBean);
}
listener.onAddressSelected(result);
}
}
private void callbackInternalno() {
if (listener != null) {
ArrayList<ISelectAble> result = new ArrayList<>(allDatas.size()-1);
for (int i = 0; i < selectDeep-1; i++) {
ISelectAble resultBean = allDatas.get(i) == null
|| selectedIndex[i] == INDEX_INVALID ? null : allDatas.get(i).get(selectedIndex[i]);
result.add(resultBean);
}
listener.onAddressSelected(result);
}
}
private void updateProgressVisibility() {
ListAdapter adapter = listView.getAdapter();
int itemCount = adapter.getCount();
progressBar.setVisibility(itemCount > 0 ? View.GONE : View.VISIBLE);
}
public SelectedListener getOnAddressSelectedListener() {
return listener;
}
public void setSelectedListener(SelectedListener listener) {
this.listener = listener;
}
}

View File

@ -0,0 +1,26 @@
package chihane.jdaddressselector.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ListView;
public class UninterceptableListView extends ListView {
public UninterceptableListView(Context context) {
super(context);
}
public UninterceptableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public UninterceptableListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.onTouchEvent(ev);
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="225"
android:fromXDelta="0%"
android:fromYDelta="100%"
android:interpolator="@android:anim/decelerate_interpolator"
android:toXDelta="0%"
android:toYDelta="0%" />

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="195"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:interpolator="@android:anim/accelerate_interpolator"
android:toXDelta="0%"
android:toYDelta="100%" />

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/tab_text_sel" android:state_enabled="false" />
<item android:color="@android:color/black" android:state_enabled="true" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<HorizontalScrollView
android:scrollbars="none"
android:id="@+id/layout_hs_tab"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/layout_tab"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
<View
android:id="@+id/indicator"
android:layout_width="0dp"
android:layout_height="2dp"
android:layout_below="@+id/layout_hs_tab"
android:background="@color/tab_text_sel" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e8e8e8" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<chihane.jdaddressselector.widget.UninterceptableListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@null" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
</LinearLayout>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/selector_text_color_tab"
android:textSize="14sp" />
<ImageView
android:id="@+id/imageViewCheckMark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_toEndOf="@+id/textView"
android:layout_toRightOf="@+id/textView"
android:contentDescription="@string/desc_check_mark"
android:src="@drawable/chose_card"
android:visibility="gone" />
</RelativeLayout>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/tab"
>
</TextView>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="tab_text_sel">#8D2316</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="desc_check_mark">check mark</string>
</resources>

View File

@ -0,0 +1,29 @@
<resources>
<style name="tab">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:ellipsize">end</item>
<item name="android:maxLines">1</item>
<item name="android:padding">10dp</item>
<item name="android:text">请选择</item>
<item name="android:textColor">@color/selector_text_color_tab</item>
<item name="android:textSize">14sp</item>
<item name="android:visibility">gone</item>
</style>
<style name="bottom_dialog" parent="android:style/Theme.Dialog">
<item name="android:windowFrame">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<style name="Animation_Bottom_Dialog">
<item name="android:windowEnterAnimation">@anim/bottom_dialog_enter</item>
<item name="android:windowExitAnimation">@anim/bottom_dialog_exit</item>
</style>
</resources>

6
settings.gradle Normal file
View File

@ -0,0 +1,6 @@
include ':autosize'
include ':zbarlibary'
include ':pager-bottom-tab-strip'
include ':uikit'
include ':app'
include ':selector'

164
uikit/AndroidManifest.xml Normal file
View File

@ -0,0 +1,164 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.netease.nim.uikit">
<application>
<activity android:name=".business.session.activity.WatchMultiRetweetPictureActivity"></activity>
<meta-data
android:name="com.netease.nim.uikit.support.glide.NIMGlideModule"
android:value="GlideModule" />
<!-- 会话窗口 -->
<!-- UI组件中包含了语音选文字功能该界面是全屏显示为了视觉上的美观该界面主题ActionBar使用Overlay模式。
如果开发者不需要该功能或效果,使用普通主题即可。 同时还需要将message_activity.xml这个layout中的根节点的paddingTop去掉。 -->
<activity
android:name=".business.session.activity.P2PMessageActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:windowSoftInputMode="stateHidden|adjustResize" />
<activity
android:name=".business.session.activity.TeamMessageActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden|adjustResize" /> <!-- 群聊 -->
<activity
android:name=".business.team.activity.NormalTeamInfoActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden|adjustResize" />
<activity
android:name=".business.team.activity.AdvancedTeamInfoActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden|adjustResize" />
<activity
android:name=".business.team.activity.AdvancedTeamMemberActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden|adjustResize" />
<activity
android:name=".business.team.activity.AdvancedTeamAnnounceActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden|adjustResize" />
<activity
android:name=".business.team.activity.AdvancedTeamMemberInfoActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden|adjustResize" />
<activity
android:name=".business.team.activity.AdvancedTeamNicknameActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden|adjustResize" />
<activity
android:name=".business.team.activity.AdvancedTeamCreateAnnounceActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden|adjustResize" />
<activity
android:name=".business.team.activity.TeamPropertySettingActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden|adjustResize" /> <!-- 联系人选择器 -->
<activity
android:name=".business.contact.selector.activity.ContactSelectActivity"
android:configChanges="keyboardHidden|orientation"
android:label="@string/contact_selector"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden|adjustPan" /> <!-- @ 选择器 -->
<activity
android:name=".business.ait.selector.AitContactSelectorActivity"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar" /> <!-- 视频 -->
<activity
android:name=".business.session.activity.CaptureVideoActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden|adjustResize" />
<activity
android:name=".business.session.activity.WatchVideoActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/video_play"
android:screenOrientation="portrait"
android:theme="@style/DarkTheme" />
<!-- 查看大图 -->
<activity
android:name=".business.session.activity.WatchMessagePictureActivity"
android:configChanges="keyboardHidden"
android:theme="@style/DarkTheme" />
<activity
android:name=".business.session.activity.WatchPicAndVideoMenuActivity"
android:configChanges="keyboardHidden"
android:screenOrientation="portrait"
android:theme="@style/DarkTheme" /> <!-- 照片选择 -->
<activity android:name=".business.session.activity.MsgSelectActivity"
android:configChanges="keyboardHidden"
/>
<provider
android:name=".common.media.model.GenericFileProvider"
android:authorities="${applicationId}.generic.file.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<activity
android:name=".common.media.imagepicker.ui.ImageGridActivity"
android:configChanges="orientation|screenSize"
android:screenOrientation="portrait"
android:theme="@style/LightBaseTheme" />
<activity
android:name=".common.media.imagepicker.ui.ImageCropActivity"
android:configChanges="orientation|screenSize"
android:hardwareAccelerated="false"
android:screenOrientation="portrait"
android:theme="@style/ImagePickerTheme.BlackStatusBar" />
<activity
android:name=".common.media.imagepicker.ui.ImagePreviewActivity"
android:configChanges="orientation|screenSize"
android:theme="@style/ImagePickerTheme.BlackStatusBar" />
<activity
android:name=".common.media.imagepicker.ui.ImagePreviewRetakeActivity"
android:configChanges="orientation|screenSize"
android:theme="@style/ImagePickerTheme.BlackStatusBar" />
<activity
android:name=".common.media.imagepicker.ui.ImageTakeActivity"
android:configChanges="orientation|screenSize"
android:screenOrientation="portrait"
android:theme="@style/ImagePickerTheme.BlackStatusBar" />
<activity
android:name=".common.media.imagepicker.video.GLVideoActivity"
android:configChanges="orientation|screenSize"
android:screenOrientation="portrait"
android:theme="@style/VideoTheme" />
<activity
android:name=".common.media.imagepicker.video.GLVideoConfirmActivity"
android:configChanges="orientation|screenSize"
android:screenOrientation="portrait"
android:theme="@style/VideoTheme" />
<activity
android:name=".common.media.imagepicker.camera.CaptureActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
</application>
</manifest>

Some files were not shown because too many files have changed in this diff Show More