Andoid螢幕適配終極手段(小編用過最得勁的dp適配)
小編嘗試過2種螢幕適配方法:
1.PX適配
使用PXGenerator程式碼生成各種解析度的資料夾以及檔案,
以某解析度比如480×800為基準,1px=1px,
按比例生成其他各種解析度的dimen檔案,會有1px=4px,1px=3px之類的情況,
以次達到螢幕適配的目的
<dimen name="py11">14.7px</dimen>
<dimen name="py12">16.0px</dimen>
<dimen name="py13">17.3px</dimen>
<dimen name="py14">18.7px</dimen>
<dimen name="py15">20.0px</dimen>
<dimen name="py16">21.3px</dimen>
<dimen name="py17">22.7px</dimen>
<dimen name="py18">24.0px</dimen>
<dimen name="py19">25.3px</dimen>
<dimen name="py20">26.7px</dimen>
<dimen name="py21">28.0px</dimen>
<dimen name="py22">29.3px</dimen>
<dimen name="py23">30.7px</dimen>
<dimen name="py24">32.0px</dimen>
<dimen name="py25">33.3px</dimen>
<dimen name="py26">34.7px</dimen>
<dimen name="py27">36.0px</dimen>
<dimen name="py28">37.3px</dimen>
<dimen name="py29">38.7px</dimen>
public class PXGenerator {
private static final String HEAD="<?xml version="1.0" encoding="utf-8"?>
";//頭部
private static final String START_TAG="<resources>
";//開始標籤
private static final String END_TAG="</resources>
";//結束標籤
private static final String ROOT="F:\AndroidStudioWorkSpace\ScreenAdaptation\app\src\main\res\values-2560x1800\";//資料夾
private static final String FILE_NAME="dimen_py2560.xml";//檔名
private static final String path=ROOT+FILE_NAME;//檔案路徑
private static final float TIMES=1920*1.0f/2560;
private static final int DIMENSION=1920;//以次為基準
public static void main(String[] args) {
generateXMl();
}
private static void generateXMl()
{
try
{
File diectoryFile = new File(ROOT);
if(!diectoryFile.exists()){
diectoryFile.mkdirs();
}
File file = new File(path);
if(file.exists()){
file.delete();
}
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(HEAD);
fileWriter.write(START_TAG);
for(int i=0;i<=DIMENSION;i++){
String output=" <dimen name="py"+i+"">"+roundString(i)+"px</dimen>
";
fileWriter.write(output);
}
fileWriter.write(END_TAG);
fileWriter.flush();
fileWriter.close();
System.out.println("寫入成功");
} catch (IOException e) {
e.printStackTrace();
System.out.println("寫入失敗");
}
}
private static String roundString(int data)
{
String result="";
float floatResult=data/TIMES;
DecimalFormat df = new DecimalFormat("0.00");
result = df.format(floatResult);
return result;
}
}
缺點:需要生成的相關解析度的dimen實在太多,增大APK包體積
2.DP適配由來
先熟知如下圖所示的各種引數
private String getScreenParams() {
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int heightPixels = dm.heightPixels;//高的畫素
int widthPixels = dm.widthPixels;//寬的畫素
int densityDpi = dm.densityDpi;//dpi
float xdpi = dm.xdpi;//xdpi
float ydpi = dm.ydpi;//ydpi
float density = dm.density;//density=dpi/160,密度比
float scaledDensity = dm.scaledDensity;//scaledDensity=dpi/160 字型縮放密度比
float heightDP = heightPixels / density;//高度的dp
float widthDP = widthPixels / density;//寬度的dp
String str = "heightPixels: " + heightPixels + "px";
str += "
widthPixels: " + widthPixels + "px";
str += "
densityDpi: " + densityDpi + "dpi";
str += "
xdpi: " + xdpi + "dpi";
str += "
ydpi: " + ydpi + "dpi";
str += "
density: " + density;
str += "
scaledDensity: " + scaledDensity;
str += "
heightDP: " + heightDP + "dp";
str += "
widthDP: " + widthDP + "dp";
return str;
}
1.px:
螢幕解析度:在橫縱向上的畫素點數。單位:px即1px=1個畫素點。
一般以縱向畫素x橫向畫素表示,如1920×1080
2.dpi:
螢幕畫素密度,指每英寸上的畫素點數,dot per inch的縮寫,與螢幕尺寸和螢幕解析度有關。以Nexus5為例,官方引數為1920*1080,dpi=445,4.95 inch 那麼,這個445的dpi是怎麼算出來的呢?由上面介紹可知,螢幕尺寸4.95是螢幕對角線的長度,而dpi是指每英寸上的畫素點數,所以應該由對角線所佔的畫素值除以4.95,如下:
3.dp/dip:
dp和dip是一樣的,密度無關畫素,Density Independent Pixels的縮寫,以160dpi為基準。在160dpi裝置 上1dp=1px,在240dpi裝置上1dp=1.5px,以此類推
4.density:
density=dpi/160,密度比
5.scaledDensity :
scaledDensity=dpi/160 字型縮放密度比
6.heightDP:
heightDP = heightPixels / density;//高度的dp
7.widthDP:
heightDP = widthPixels / density;//寬度的dp
先看上圖sw< N >dp:
smallestWidth,最小寬度,
官方提供了多種尺寸限制符,sw指的是最小寬度,和豎屏橫屏無關:
如800DPx480DP(最小寬度是480dp,比如部分800×480手機),
592DPx360DP(最小寬度是360dp,比如部分1920×1080手機),
604DPx360DP(最小寬度是360dp,比如部分1920×1080手機),
680DPx360DP(最小寬度是360dp.比如部分2160×1080手機(2018年左右開始流行長螢幕手機、劉海屏手機)),
1232DPx900DP(最小寬度是900dp,比如部分2560×1800手機(平板))
這裡發現多種解析度手機最小寬度都是360DP,尤其是1920×1080,是2018年左右最流行的螢幕解析度,因此考慮以寬度360dp為基準做適配(這也是為何使用DP適配,而不使用PX適配的原因,因為DP適配需要的dimens檔案會少很多)
sw< N >dp會向上相容:
比如valuse-sw481dp資料夾,當且僅當手機最小寬度dp>=481dp才會去該目錄尋找數值,800DPx480DP最小寬度480dp,是無法進入valuse-sw481dp尋找數值的,如果values目錄下沒有對應的數值,只有一個結果GG=APP崩潰。所以任何時候都記得一定要在values目錄下建立對應的數值
800DPx480DP手機,由於沒有valuse-sw480dp資料夾,只能向下尋找,找到valuse-sw100dp資料夾,結果介面當然很醜,(適配具體操作 容後再談)
上文說到:多種解析度手機最小寬度都是360DP,尤其是1920×1080,是2018年左右最流行的螢幕解析度,因此考慮以寬度360dp為基準做適配
3.DP適配原理以及具體操作
常見的swdp如下:
// private static final int[] dps = {360, 384, 392, 400, 410, 411, 480, 533, 592,
// 600, 640, 662, 720, 768, 800, 811, 820,900, 960, 961, 1024, 1280};//常見dp列表
為了適配更多手機,dp應儘可能包含更多,而且連續性強,間隔小
dp適配原理:
和px適配原理類似(按比例計算),以sw360dp為基準,
<dimen name="height_title">48dp</dimen>
<dimen name="sp18">18sp</dimen>
<dimen name="padding_icon">12dp</dimen>
<dimen name="dp360">360dp</dimen>
<dimen name="sp10">10sp</dimen>
<dimen name="dp30">30dp</dimen>
<dimen name="dp10">10dp</dimen>
<dimen name="sp14">14sp</dimen>
<dimen name="dp40">40dp</dimen>
<dimen name="dp70">70dp</dimen>
<dimen name="dp100">100dp</dimen>
<dimen name="dp80">80dp</dimen>
<dimen name="sp12">12sp</dimen>
<dimen name="dp60">60dp</dimen>
<dimen name="_dp10">-10dp</dimen>
<dimen name="dp4_5">4.5dp</dimen>
<dimen name="_dp5_5">-5.5dp</dimen>
那麼sw720dp如下:
全是2倍值
<dimen name="height_title">96dp</dimen>
<dimen name="sp18">36sp</dimen>
<dimen name="padding_icon">24dp</dimen>
<dimen name="dp360">720dp</dimen>
<dimen name="sp10">20sp</dimen>
<dimen name="dp30">60dp</dimen>
<dimen name="dp10">20dp</dimen>
<dimen name="sp14">28sp</dimen>
<dimen name="dp40">80dp</dimen>
<dimen name="dp70">140dp</dimen>
<dimen name="dp100">200dp</dimen>
<dimen name="dp80">160dp</dimen>
<dimen name="sp12">24sp</dimen>
<dimen name="dp60">120dp</dimen>
<dimen name="_dp10">-20dp</dimen>
<dimen name="dp4_5">9dp</dimen>
<dimen name="_dp5_5">-11dp</dimen>
具體操作:
1)
以sw360dp為基準,按照1920×1080的UI設計圖,可以讓設計師用“標你妹”等軟體標註dp和sp,也可以上傳到藍湖,藍湖上的標註更爽,在values下的dimens檔案中定義dp值對應的dimen標籤。
比如,UI圖上標註的一個按鈕高度是40dp,那麼在dimens檔案中建立
<dimen name="height_btn_">40dp</dimen>
name名字可以隨便取
2)
將如下程式碼類放到當前android專案的app module下,記得更改root目錄為自己的android專案的目錄,
public class DPGeneratorLittle {
private static final String HEAD = "<?xml version="1.0" encoding="utf-8"?>
";//頭部
private static final String START_TAG = "<resources>
";//開始標籤
private static final String END_TAG = "</resources>
";//結束標籤
private static final float DP_BASE = 360;//360dp為基準
private static final int DP_MAX = 720;//所有dimens檔案dp從0生成到這個值
private static final int SP_MAX = 48;//SP最大
private static final int[] dps = {360, 384, 392, 400, 410, 411, 480, 533, 592,
600, 640, 662, 720, 768, 800, 811, 820,900, 960, 961, 1024, 1280};//常見dp列表
// private static final int[] dps = {100,481,510,720,900};//常見dp列表
private static final String root="F:\AndroidStudioWorkSpace\ScreenAdaptation\app\src\main\res\";//生成檔案的主目錄
private static ExecutorService fixedThreadPool;//執行緒池,用於生成XML檔案
private static int size_thread = 5;//執行緒池大小
private static DocumentBuilderFactory dbFactory;
private static DocumentBuilder db;
private static Document document;
public static void main(String[] args) {
try {
dbFactory = DocumentBuilderFactory.newInstance();
db = dbFactory.newDocumentBuilder();
//將給定 URI 的內容解析為一個 XML 文件,並返回Document物件
//記得改成自己當前專案的路徑
document = db.parse(root+"values\dimens.xml");
//按文件順序返回包含在文件中且具有給定標記名稱的所有 Element 的 NodeList
NodeList dimenList = document.getElementsByTagName("dimen");
if (dimenList.getLength()==0)return;
List<Dimen> list = new ArrayList<>();
for (int i = 0; i < dimenList.getLength(); i++) {
//獲取第i個book結點
Node node = dimenList.item(i);
//獲取第i個dimen的所有屬性
NamedNodeMap namedNodeMap = node.getAttributes();
//獲取已知名為name的屬性值
String atrName = namedNodeMap.getNamedItem("name").getTextContent();
String value = node.getTextContent();
System.out.println("+++atrName++++++++++++++++++++" + atrName);
System.out.println("+++++++++++++value++++++++++" + value);
list.add(new Dimen(atrName, value));
}
fixedThreadPool = Executors.newFixedThreadPool(size_thread);
for (int i = 0; i < dps.length; i++) {
XMLThread xmlThread = new XMLThread(i, list);
fixedThreadPool.execute(xmlThread);//執行緒啟動執行
}
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
}
private static class XMLThread implements Runnable {
private int index = 0;
private List<Dimen> list;
public XMLThread(int index, List<Dimen> list) {
this.index = index;
this.list = list;
}
@Override
public void run() {
//記得改成自己當前專案的路徑
generateXMl(list, index, root+"values-sw" + dps[index] + "dp\", "dimens.xml");
}
}
private static void generateXMl(List<Dimen> list, int index, String pathDir, String fileName) {
try {
File diectoryFile = new File(pathDir);
if (!diectoryFile.exists()) {
diectoryFile.mkdirs();
}
File file = new File(pathDir + fileName);
if (file.exists()) {
file.delete();
}
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(HEAD);
fileWriter.write(START_TAG);
//?????????????????????????????????????????????
int size = list.size();
String atrName;
String value;
for (int i = 0; i < size; i++) {
atrName = list.get(i).getAtrName();
value = list.get(i).getValue();
String output = " <dimen name="" + atrName + "">" +
roundString(Float.valueOf(value.substring(0, value.length() - 2)), index) +
value.substring(value.length()-2)+"</dimen>
";
fileWriter.write(output);
}
fileWriter.write(END_TAG);
fileWriter.flush();
fileWriter.close();
System.out.println("寫入成功");
} catch (IOException e) {
e.printStackTrace();
System.out.println("寫入失敗");
}
}
//精確到小數點後2位,並且四捨五入(因為有SW1280dp,基準是160dp,1dp=1px,
// 如果精確到小數點後一位,四捨五入會有0.5dp誤差,在sw1280dp中會有4PX誤差,精確到小數點後2位,四捨五入,誤差控制在1PX之內)
private static String roundString(float data, int index) {
String result = "";
float floatResult = data * dps[index] / DP_BASE;
DecimalFormat df = new DecimalFormat("0.00");
result = df.format(floatResult);
return result;
}
private static class Dimen {
private String atrName;
private String value;
public Dimen(String atrName, String value) {
this.atrName = atrName;
this.value = value;
}
public String getAtrName() {
return atrName;
}
public void setAtrName(String atrName) {
this.atrName = atrName;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}
如圖執行工具類的main方法,即可生成各種dimens檔案
3)使用的相關原理及技巧
1.工具類解析values目錄下的dimens檔案,根據其中所有的dimen標籤,name和values,生成其他各種dp對應的dimens檔案
2.DP_BASE可修改為自己定的基準(你可以根據實際情況定為其他dp,一般情況下,定義為360dp,因為大部分手機sw都是360dp)
3.root對應的是當前android專案的主目錄路徑,千萬不能出錯
4.size_thread,執行緒池大小預設5,你可以設定其他數值
5.你還可以根據自己強迫症的各種習慣和喜好,各種更改成自己喜歡的風格,程式碼很精簡,修改很容易
6.在每次打包(如果需要除錯sw360dp之外的手機,或者上線APP)之前,記得執行DPGeneratorLittle的main方法,以確保所有的dimen檔案都是最新的
7.負數的dp,小數的dp,甚至小數的sp(一般沒這情況)都可以生成,放心大膽使用
8.如果發現某臺手機適配效果不是很好,那麼真機除錯,得到其swdp,然後在用DPGeneratorLittle生成對應的swdp,dimen檔案
9.執行DPGeneratorLittle報錯的時候,記得刪除所有生成的valuse目錄,clean專案,重新執行
4.欣賞DP適配案例
800PXx480PX的手機
1920PXx1080PX的手機
2560PXx1800PX的手機
**可以看到適配效果很好,幾乎沒有無法接受的地方
程式碼如下:**
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="@dimen/height_title"
android:background="#1d953f"
android:gravity="center_vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@+id/iv_back"
android:gravity="center_vertical"
android:text="螢幕適配"
android:textColor="#ffffff"
android:textSize="@dimen/sp18" />
<ImageView
android:id="@+id/iv_back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:padding="@dimen/padding_icon"
android:scaleType="centerInside"
android:src="@drawable/back" />
</RelativeLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp360">
<ImageView
android:id="@+id/imageView"
android:layout_width="@dimen/dp360"
android:layout_height="@dimen/dp360"
android:layout_gravity="center_horizontal"
android:scaleType="centerCrop"
android:src="@drawable/rec" />
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textColor="#fff"
android:textSize="@dimen/sp18" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<View
android:layout_width="@dimen/dp40"
android:layout_height="@dimen/dp40"
android:layout_marginLeft="@dimen/_dp5_5"
android:background="#436876" />
<com.cy.screenadaptation.ViewCanMeasure
android:id="@+id/view_bottom"
android:layout_width="@dimen/dp60"
android:layout_height="@dimen/dp60"
android:background="#00f"
android:gravity="center"
android:textColor="#fff"
android:textSize="@dimen/sp12" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<View
android:layout_width="@dimen/dp4_5"
android:layout_height="@dimen/dp40"
android:layout_marginRight="@dimen/dp10"
android:background="#00f" />
<View
android:layout_width="@dimen/dp40"
android:layout_height="@dimen/dp40"
android:layout_marginRight="@dimen/_dp10"
android:background="#f00" />
</LinearLayout>
</LinearLayout>
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
tv.setText(getScreenParams());
}
private String getScreenParams() {
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int heightPixels = dm.heightPixels;//高的畫素
int widthPixels = dm.widthPixels;//寬的畫素
int densityDpi = dm.densityDpi;//dpi
float xdpi = dm.xdpi;//xdpi
float ydpi = dm.ydpi;//ydpi
float density = dm.density;//density=dpi/160,密度比
float scaledDensity = dm.scaledDensity;//scaledDensity=dpi/160 字型縮放密度比
float heightDP = heightPixels / density;//高度的dp
float widthDP = widthPixels / density;//寬度的dp
String str = "heightPixels: " + heightPixels + "px";
str += "
widthPixels: " + widthPixels + "px";
str += "
densityDpi: " + densityDpi + "dpi";
str += "
xdpi: " + xdpi + "dpi";
str += "
ydpi: " + ydpi + "dpi";
str += "
density: " + density;
str += "
scaledDensity: " + scaledDensity;
str += "
heightDP: " + heightDP + "dp";
str += "
widthDP: " + widthDP + "dp";
return str;
}
}
dp適配優點:dimens檔案很少,不影響apk包的體積,
缺點:在每次打包(如果需要除錯sw360dp之外的手機,或者上線APP)之前,都需要執行DPGeneratorLittle的main方法,以確保所有的dimen檔案都是最新的,有點煩躁
想不煩躁可以,你可以用github_DPScreenAdaptation的DPGenerator類
直接生成所有可能用到的dp,sp,但是這樣,可能會增加好幾百KB檔案
各位老鐵有問題歡迎及時聯絡、指正、批評、撕逼
關注專題Android開發常用開源庫
微信公眾號
QQ群
相關文章
- 螢幕適配
- Android 螢幕適配終結者Android
- android螢幕適配三:通過畫素密度適配Android
- flutter 螢幕尺寸適配 字型大小適配Flutter
- Flutter螢幕適配Flutter
- AutoLayout螢幕適配
- android 螢幕適配Android
- 極其簡單的Flutter 螢幕適配Flutter
- Android螢幕適配(理論適配100%機型)Android
- Android技能樹 — 螢幕適配小結Android
- android 螢幕適配一:通過自定義View的方式實現適配AndroidView
- Android 主流螢幕以及適配Android
- 【postcss-px-to-viewport】螢幕適配CSSView
- Flutter螢幕適配 - 等比縮放Flutter
- @media 移動端螢幕適配
- Android dp方式的螢幕適配工具使用(Android Studio外掛方式)Android
- android 今日頭條的螢幕適配理解Android
- 移動 web 端螢幕適配 – remWebREM
- Android螢幕適配總結和思考Android
- H5 分層螢幕適配H5
- android 螢幕適配二:手寫百分比佈局適配Android
- Flutter螢幕適配,簡單粗暴的全域性適配方式Flutter
- 淺談-web螢幕適配的解決方案Web
- Android螢幕適配前先了解這些Android
- Android適配:DP簡述Android
- Android APP全方位效能調優之螢幕適配終結者AndroidAPP
- 學習筆記:自適應佈局,多螢幕適配筆記
- Android最全螢幕適配的幾個重要概念(三)Android
- Android 螢幕適配:最全面的解決方案Android
- cocos creator螢幕適配的一些知識點
- [譯] Flutter —— 根據不同螢幕尺寸高效的適配 UIFlutterUI
- flutter螢幕適配 ,一種一勞永逸的全域性適配方式Flutter
- 鴻蒙Banner圖一多適配不同螢幕鴻蒙
- 實踐 | 為 Trackr app 適配大螢幕裝置APP
- 全志T113-i開發板適配LVDS螢幕的過程
- Android適配: 拉伸適配的缺點Android
- Xcode11 Luanchscreen.storyboard 複雜元素螢幕適配XCode
- 移動APP測試-Android螢幕適配問題(一)APPAndroid