Hướng dẫn hỗ trợ đa màn hình trong lập trình Android

android fragmentation

Trong quá trình phát triển Android thì mình nhận ra một vấn đề rất lớn của nền tảng mobile có thị phần lớn nhất thế giới đó chính là sự phân mảnh thiết bị, cộng với vô số các kích thước màn hình. Với một newbie thì đây thực sự là cơn ác mộng, đó cũng là lý do các bạn thực tập thường ngại làm với UI/UX.

Vậy thì làm thế nào để có thể hỗ trợ nhiều độ phân giải màn hình cho điện thoại Android, bạn có thể tham khảo bài viết bên dưới nhé.

Bước 1: Tạo file multiscreen.gradle nằm trong thư mục chính của Project

task convertDimens() {
    def xmlRoot = "${project.rootDir}/app/src/main/res/values"
    def xmlDimenFileName = "dimens.xml"
    def xmlFileDefault = xmlRoot + "/" + xmlDimenFileName
    def xmlFile360 = xmlRoot + "-sw360dp/" + xmlDimenFileName
    def xmlFile480 = xmlRoot + "-sw480dp/" + xmlDimenFileName
    def xmlFile540 = xmlRoot + "-sw540dp/" + xmlDimenFileName
    def xmlFile600 = xmlRoot + "-sw600dp/" + xmlDimenFileName
    def xmlFile640 = xmlRoot + "-sw640dp/" + xmlDimenFileName
    def xmlFile720 = xmlRoot + "-sw720dp/" + xmlDimenFileName
    def xmlFile800 = xmlRoot + "-sw800dp/" + xmlDimenFileName

    makeFolder(xmlFile360, xmlFile480, xmlFile540, xmlFile600, xmlFile640, xmlFile720, xmlFile800)

    createOrCloneDefaultData(xmlFileDefault, xmlFile360)

    def xmlOriginal = new XmlParser().parse(xmlFile360)

    def xml480 = xmlOriginal.clone()
    def xml540 = xmlOriginal.clone()
    def xml600 = xmlOriginal.clone()
    def xml640 = xmlOriginal.clone()
    def xml720 = xmlOriginal.clone()
    def xml800 = xmlOriginal.clone()

    xml480.dimen.each { dimen ->
        def value = dimen.text()
        if (isSkipCheck(value)) {
            //skip
        } else if (value.contains("px")) {
            def newValue = value.replace("px", "").toFloat()
            newValue = round((480 / 360 * newValue).toFloat(), 1)
            dimen.value = newValue + "px"
        } else {
            def newValue = value.replace("dp", "").toFloat()
            newValue = round((480 / 360 * newValue).toFloat(), 1)
            dimen.value = newValue + "dp"
        }
    }

    xml540.dimen.each { dimen ->
        def value = dimen.text()
        if (isSkipCheck(value)) {
            //skip
        } else if (value.contains("px")) {
            def newValue = value.replace("px", "").toFloat()
            newValue = round((540 / 360 * newValue).toFloat(), 1)
            dimen.value = newValue + "px"
        } else {
            def newValue = value.replace("dp", "").toFloat()
            newValue = round((540 / 360 * newValue).toFloat(), 1)
            dimen.value = newValue + "dp"
        }
    }
    xml600.dimen.each { dimen ->
        def value = dimen.text()
        if (isSkipCheck(value)) {
            //skip
        } else if (value.contains("px")) {
            def newValue = value.replace("px", "").toFloat()
            newValue = round((600 / 360 * newValue).toFloat(), 1)
            dimen.value = newValue + "px"
        } else {
            def newValue = value.replace("dp", "").toFloat()
            newValue = round((600 / 360 * newValue).toFloat(), 1)
            dimen.value = newValue + "dp"
        }
    }
    xml640.dimen.each { dimen ->
        def value = dimen.text()
        if (isSkipCheck(value)) {
            //skip
        } else if (value.contains("px")) {
            def newValue = value.replace("px", "").toFloat()
            newValue = round((640 / 360 * newValue).toFloat(), 1)
            dimen.value = newValue + "px"
        } else {
            def newValue = value.replace("dp", "").toFloat()
            newValue = round((640 / 360 * newValue).toFloat(), 1)
            dimen.value = newValue + "dp"
        }
    }
    xml720.dimen.each { dimen ->
        def value = dimen.text()
        if (isSkipCheck(value)) {
            //skip
        } else if (value.contains("px")) {
            def newValue = value.replace("px", "").toFloat()
            newValue = round((720 / 360 * newValue).toFloat(), 1)
            dimen.value = newValue + "px"
        } else {
            def newValue = value.replace("dp", "").toFloat()
            newValue = round((720 / 360 * newValue).toFloat(), 1)
            dimen.value = newValue + "dp"
        }
    }
    xml800.dimen.each { dimen ->
        def value = dimen.text()
        if (isSkipCheck(value)) {
            //skip
        } else if (value.contains("px")) {
            def newValue = value.replace("px", "").toFloat()
            newValue = round((800 / 360 * newValue).toFloat(), 1)
            dimen.value = newValue + "px"
        } else {
            def newValue = value.replace("dp", "").toFloat()
            newValue = round((800 / 360 * newValue).toFloat(), 1)
            dimen.value = newValue + "dp"
        }
    }

    new XmlNodePrinter(new PrintWriter(new FileWriter(xmlFile480))).print(xml480)
    new XmlNodePrinter(new PrintWriter(new FileWriter(xmlFile540))).print(xml540)
    new XmlNodePrinter(new PrintWriter(new FileWriter(xmlFile600))).print(xml600)
    new XmlNodePrinter(new PrintWriter(new FileWriter(xmlFile640))).print(xml640)
    new XmlNodePrinter(new PrintWriter(new FileWriter(xmlFile720))).print(xml720)
    new XmlNodePrinter(new PrintWriter(new FileWriter(xmlFile800))).print(xml800)
}

private static boolean isSkipCheck(String value) {
    return value.contains("sp") || value.contains("@dimen/")
}

private static void createOrCloneDefaultData(String xmlFileDefault, String xmlFile360) {
    def fileDefault = new File(xmlFileDefault)
    if (!fileDefault.exists()) {
        fileDefault.createNewFile()
        StringBuilder content = new StringBuilder("<resources>\n")
        for (int i = 2; i <= 100; i += 2) {
            content.append("\t<dimen name=\"dp" + i + "\">" + i + "dp</dimen>\n")
        }
        content.append("</resources>")
        BufferedWriter bw = new BufferedWriter(new FileWriter(xmlFileDefault))
        bw.write(content.toString())
        bw.close()
    }

    def file360 = new File(xmlFile360)
    if (fileDefault.exists()) {
        copyFileUsingStream(fileDefault, file360)
    }
}

static float round(float d, int decimalPlace) {
    BigDecimal bd = new BigDecimal(Float.toString(d))
    bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP)
    return bd.floatValue()
}

static void makeFolder(String... paths) {
    for (int i = 0; i < paths.length; i++) {
        def folder = new File(new File(paths[i]).getParent())
        if (!folder.exists()) {
            folder.mkdirs()
        }
    }
}

static void copyFileUsingStream(File source, File dest) throws IOException {
    InputStream is = null
    OutputStream os = null
    try {
        is = new FileInputStream(source)
        os = new FileOutputStream(dest)
        byte[] buffer = new byte[1024]
        int length
        while ((length = is.read(buffer)) > 0) {
            os.write(buffer, 0, length)
        }
    } finally {
        if (is != null) {
            is.close()
        }
        if (os != null) {
            os.close()
        }
    }
}

Bước 2: Import vào module app

apply from: '../multiscreen.gradle'

android {
   clean.dependsOn convertDimens
}

Chúc mừng bạn, việc tích hợp đã hoàn thành!

Kết quả sau khi tích hợp:

Script sẽ tự generate các giá trị tương ứng với từng độ phân giải màn hình. Công thức tính dựa theo độ phân giải 360dp

Với những giá trị bạn không muốn script can thiệp, bạn có thể bỏ qua bằng cách tạo thêm file fixed_dimens.xml trong folder values là được nha.

Đây là những kinh nghiệm trong quá trình làm việc mà mình và các đồng nghiệp tại tập thể công ty Cổ phần Segu đã tìm ra. Rất mong các bạn ủng hộ.

Loading

Là một người thích chia sẻ, tôi tạo ra blog này để mọi người - đặc biệt là các bạn mới vào nghề biết thêm được những kiến thức hữu ích. Rất mong nó sẽ có ích với bạn.

70 thoughts on “Hướng dẫn hỗ trợ đa màn hình trong lập trình Android

  1. It s so hard for me personally at this age and stage of my life to pin point what is normal and what could possibly be a side effect priligy canada Sacubitril valsartan improved congestion to a greater extent than did enalapril

  2. Thank you for your sharing. I am worried that I lack creative ideas. It is your article that makes me full of hope. Thank you. But, I have a question, can you help me?

  3. Mostbet Colombia – una plataforma líder para juegos de casino en Colombia.

    Descarga la app y obtén acceso a estadísticas en tiempo real .

  4. Thank you for your sharing. I am worried that I lack creative ideas. It is your article that makes me full of hope. Thank you. But, I have a question, can you help me?

  5. Always available support – we’re ready to help!
    If you have any questions, the Pin-Up Casino support team is here for you.
    Reach out to us via chat, email, or Telegram for quick assistance and resolutions
    to your issues.
    pin up casino app
    Should You Download? Pin-Up offers a mobile version for both Android
    devices and iOS, which makes it so much easier.
    But is the app really worth it? I’ve been playing on it for a while,
    and it’s super user-friendly. Has anyone else had
    the same experience? Is it speedier than the PC site?

  6. MELBET বাংলাদেশের বিশ্বমানের বেটিং প্ল্যাটফর্ম। আকর্ষণীয় বোনাস,
    প্রচারমূলক অফার এবং
    প্রতিদিনের বেটিং সুযোগ দিয়ে আপনার গেমিং অভিজ্ঞতাকে পরবর্তী স্তরে নিয়ে যান!
    melbet

  7. Thank you for your sharing. I am worried that I lack creative ideas. It is your article that makes me full of hope. Thank you. But, I have a question, can you help me?

  8. Can you be more specific about the content of your article? After reading it, I still have some doubts. Hope you can help me.

  9. You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page. Those revenues came from a sports betting handle of over $811.2 million, which grew from over $726.3 million in Sept. 2023. Most of last month’s handle came from online betting sites, with operators contributing more than $765.4 million. This was enough to beat the total sports betting handle from a year ago. You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page. Parx is one of eight Pennsylvania casinos to offer sports betting, including at its sub-brands, the Turf Club and Valley Forge Turf Club, thus positioning the brand to become one of the state’s early online sports betting leaders.
    https://kartavyapathsiddhi.com/2025/05/27/local-insights-why-aviator-bet-tanzania-dominates-the-gaming-scene_1748352449/
    Belanja di App banyak untungnya: This Aztec-themed slot is jam packed full of jewels. Sebagai situs toto yang terpercaya, SLOT TOGEL138 juga menyediakan slot88, yang terkenal dengan gameplay yang mudah dan peluang kemenangan besar. Setiap permainan slot di SLOT TOGEL138 dirancang untuk memberikan pengalaman bermain yang lancar dan adil. Dengan fitur-fitur seperti free spin, multiplier, dan jackpot progresif, Anda bisa meraih kemenangan besar lebih sering. Tak heran jika banyak pemain memilih slot gacor di TOGEL138 untuk meraih maxwin lebih cepat dan mudah. The volatility of this game is high, which makes it an excellent match for our preferred slot machine strategies. Our slot strategies target the highest possible volatility. Bergabunglah sekarang di SLOT TOGEL138, situs toto slot gacor terbaik untuk bermain slot online dan slot88 dengan peluang gampang maxwin. Dapatkan pengalaman bermain yang seru dan menguntungkan dengan berbagai bonus menarik dan hadiah besar. Klik sekarang untuk menemukan slot terbaik dan mulai meraih kemenangan besar di SLOT TOGEL138!

Leave a Reply

Your email address will not be published. Required fields are marked *

Back To Top