项目适配遇到的问题,在Android 4.0以上的系统当中,用户能够在系统设置对字体大小进行更改,这样一来,在自己的应用当中由于字体大小的变化会导致显示不全,布局错乱等问题的存在。这个设置直接会影响到所有sp为单位的字体适配,所以很多app在设置了系统字体后瞬间变得面目全非。
虽然google推荐使用sp作为字体的单位,但实际的开发过程中通常是根据UI的设计稿来换算 sp(px换算sp)。而sp即使在同一种密度下其值也不尽相同。比如在240dpi的设备,如果是480x800分辨率这个值通常是1.5倍 (scaledDensity=1.5),如果是480xZ(z>800)那么这个值有可能大于1.5。这无疑给设备的适配带来更多的困难和陷阱。所以个人通常建议使用dpi来作为字体的单位。
默认情况下,字体跟随系统设置,对于个别app不需要根据系统字体的大小来改变的,可以在activity基类(app中所有的activity都应该有继承于我们自己定义的一个BaseActivity类)中加上以下代码:
@Override
public Resources getResources() {
Resources res = super.getResources();
Configuration config=new Configuration();
config.setToDefaults();
res.updateConfiguration(config,res.getDisplayMetrics() );
return res;
}
当然,我们可以提供配置项供用户选择是否字体大小跟随系统。
以下是一个例子:
布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="字体设置(重启应用生效)" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/btn0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跟随系统设置" />
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="不跟随系统设置" />
LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="sp字号" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Hello World!"
android:textSize="40sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Hello World!"
android:textSize="50sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Hello World!"
android:textSize="60sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="dp字号" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Hello World!"
android:textSize="40dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Hello World!"
android:textSize="50dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Hello World!"
android:textSize="60dp" />
LinearLayout>
RelativeLayout>
LinearLayout>
ScrollView>
LinearLayout>
BaseActivity.java
import android.app.Activity;
import android.content.res.Configuration;
import android.content.res.Resources;
/**
* BaseActivity
*/
public class BaseActivity extends Activity {
@Override
public Resources getResources() {
if ((Boolean) SPUtils.get(this, "follow", false)) {
return super.getResources();
}
Resources res = super.getResources();
Configuration config = new Configuration();
//将配置项设置为系统默认值
config.setToDefaults();
//保存更改后的配置项
res.updateConfiguration(config, res.getDisplayMetrics());
return res;
}
}
MainActivity.java
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
findViewById(R.id.btn0).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SPUtils.put(MainActivity.this, "follow", true);
Toast.makeText(MainActivity.this, "设置为跟随系统", Toast.LENGTH_SHORT).show();
}
});
findViewById(R.id.btn1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SPUtils.put(MainActivity.this, "follow", false);
Toast.makeText(MainActivity.this, "设置为不跟随系统", Toast.LENGTH_SHORT).show();
}
});
}
}
SPUtils.java
import android.content.Context;
import android.content.SharedPreferences;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
/**
* Created by bridgegeorge on 2016/11/21.
*/
public class SPUtils {
/**
* 保存在手机里面的文件名
*/
public static final String FILE_NAME = "share_data";
/**
* 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法
*
* @param context
* @param key
* @param object
*/
public static void put(Context context, String key, Object object) {
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
if (object instanceof String) {
editor.putString(key, (String) object);
} else if (object instanceof Integer) {
editor.putInt(key, (Integer) object);
} else if (object instanceof Boolean) {
editor.putBoolean(key, (Boolean) object);
} else if (object instanceof Float) {
editor.putFloat(key, (Float) object);
} else if (object instanceof Long) {
editor.putLong(key, (Long) object);
} else {
editor.putString(key, object.toString());
}
SharedPreferencesCompat.apply(editor);
}
/**
* 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值
*
* @param context
* @param key
* @param defaultObject
* @return
*/
public static Object get(Context context, String key, Object defaultObject) {
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
Context.MODE_PRIVATE);
if (defaultObject instanceof String) {
return sp.getString(key, (String) defaultObject);
} else if (defaultObject instanceof Integer) {
return sp.getInt(key, (Integer) defaultObject);
} else if (defaultObject instanceof Boolean) {
return sp.getBoolean(key, (Boolean) defaultObject);
} else if (defaultObject instanceof Float) {
return sp.getFloat(key, (Float) defaultObject);
} else if (defaultObject instanceof Long) {
return sp.getLong(key, (Long) defaultObject);
}
return null;
}
/**
* 移除某个key值已经对应的值
*
* @param context
* @param key
*/
public static void remove(Context context, String key) {
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.remove(key);
SharedPreferencesCompat.apply(editor);
}
/**
* 清除所有数据
*
* @param context
*/
public static void clear(Context context) {
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.clear();
SharedPreferencesCompat.apply(editor);
}
/**
* 查询某个key是否已经存在
*
* @param context
* @param key
* @return
*/
public static boolean contains(Context context, String key) {
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
Context.MODE_PRIVATE);
return sp.contains(key);
}
/**
* 返回所有的键值对
*
* @param context
* @return
*/
public static Map getAll(Context context) {
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
Context.MODE_PRIVATE);
return sp.getAll();
}
/**
* 创建一个解决SharedPreferencesCompat.apply方法的一个兼容类
*
* @author zhy
*/
private static class SharedPreferencesCompat {
private static final Method sApplyMethod = findApplyMethod();
/**
* 反射查找apply的方法
*
* @return
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private static Method findApplyMethod() {
try {
Class clz = SharedPreferences.Editor.class;
return clz.getMethod("apply");
} catch (NoSuchMethodException e) {
}
return null;
}
/**
* 如果找到则使用apply执行,否则使用commit
*
* @param editor
*/
public static void apply(SharedPreferences.Editor editor) {
try {
if (sApplyMethod != null) {
sApplyMethod.invoke(editor);
return;
}
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
editor.commit();
}
}
}
正常字体模式下
系统显示设置 | dp和sp字号作比对 |
显示设置调大字体,默认跟随系统设置,sp随系统设置缩放,dp没有变化。
sp字号(跟随缩放) | dp字号(不缩放) |
设置不跟随系统字体后,应用内部使用的是系统默认字体。