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.

113 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!

  10. I don’t think the title of your article matches the content lol. Just kidding, mainly because I had some doubts after reading the article.

  11. Full House Find our latest financial information, annual reports, investor events, presentations, and ESG information. Our success depends on hiring great people — people as diverse as the global communities we serve. We have mentioned some of the game-winning odds percent; Dragon Tiger offers the highest RTP in live Dragon vs Tiger bets and the lowest in favorable ties. These RTPs are somewhat similar to the Baccarat and Casino War games, because of Dragon Tiger’s similarity with both. Playing your favourite casino game from your mobile, benefiting from an increasingly unique and generous welcome bonus or the availability of the Live Dealer versions of table games at a casino online are just some of the ways that the online UK casino has improved over the years. TeenPatti GoldStar – A Thrilling Card Game
    https://arturepur1977.bearsfanteamshop.com/sa-ita-yaha
    तिरंगा कलर ट्रेडिंग गेम्स ऐप proudly of Indian origin, is dedicated to providing you with the ultimate gaming environment with a whopping 7 Tiranga App Game and trading categories and an extensive collection of over 50 casino games, just like Win Go Wingo, Aviator, Lottery, Dice, Cards, we ensure there’s something for every triangle game login enthusiast. User ratings and reviews play a crucial role in evaluating the performance and popularity of gaming apps. Daman Game has garnered high praise from users worldwide, with many commending its engaging quizzes, seamless gameplay, and secure payout options. The app’s user-friendly interface and diverse quiz topics have received positive feedback, making it a top choice among quiz enthusiasts. Additionally, users have highlighted the convenience of cashing out earnings and the enjoyable experience of participating in quiz tournaments to win big rewards. Overall, the overwhelmingly positive user ratings and reviews underscore Daman Game’s reputation as a rewarding and entertaining gaming platform.

  12. Utiliza a tua chave de CD no Steam. Se não tiver uma conta Steam, terá de descarregar o cliente a partir do site oficial, instalá-lo e depois criar a sua conta. Uma vez que tenha entrado na sua conta, clique em “Adicionar jogo” no canto inferior esquerdo e depois em “Activar produto no Steam”. Após alguns cliques em “Next”, pode finalmente introduzir a chave do seu CD, o jogo será automaticamente adicionado à sua biblioteca e o download será iniciado. Se não tiver uma conta Steam, terá de descarregar o cliente a partir do site oficial, instalá-lo e depois criar a sua conta. Uma vez que tenha entrado na sua conta, clique em “Adicionar jogo” no canto inferior esquerdo e depois em “Activar produto no Steam”. Após alguns cliques em “Next”, pode finalmente introduzir a chave do seu CD, o jogo será automaticamente adicionado à sua biblioteca e o download será iniciado.
    https://www.cricketbettingindia.in/uncategorized/review-completo-do-penalty-shoot-out-da-evoplay-para-jogadores-brasileiros/
    Para vizualizar o clipe deste CD clique aqui Ficha Técnica Dados do Arquivo: Sell your publications commission-free as single issues or ongoing subscriptions. 01. Party People (feat. T.I.)02. Hypnotize U03. Help Me04. Victory05. Perfect Defect 06. I’ve Seen The Light Inside of Clouds07. God Bless Us All 08. Life As A Fish 09. Nothing On You 10. Hot-n-Fun (feat. Nelly Furtado) 11. It’s In The Air12. Sacred Temple13. I Wanna Jam14. The Man Onde eu gostaria de ir com esta digressão é um mistério. O que fica claro é que o 2016 tem de tudo um pouco, então fica difícil definir o que aparece na nossa playlist. A presença de grandes lendas que já triunfavam em 2008 é surpreendente, mas é ainda mais surpreendente como a autoconsciência e o conhecimento do que já existe serve para criar tantos novos sons que parecem continuar semelhantes aos já ouvidos pelo passado. No entanto, basta ouvir Aesop Rock com Kirby ou Kanye West com Famous (e Rihanna) para ver o quanto o hip-hop mudou em sua própria falta de mudança. O épico em uma parte da história, a completa simplicidade na outra.

  13. Jakie są cechy charakterystyczne slotów? RTP dla łowców wampirów to 96%, aviator casino online opinie że gracze mają możliwość korzystania ze strony w trybie offline. Wiele kasyn oferuje darmowe spiny, które oferuje wiele różnych gier. Gdzie gra hazard jak wygrać automat Big Banker Deluxe, w tym wiele wariantów blackjacka na żywo. Лучшие онлайн казино с оплатой через СМС в 2025 году Онлайн казино становятся все более популярными, и одним из удобных способов пополнения счета является использование мобильных платежей, в частности, через СМС. ЭтотRead More…
    https://subscribe.ru/author/31979941
    Tak, Vavada oferuje funkcję samowykluczenia, która pozwala dobrowolnie ograniczyć dostęp do usług hazardowych. Aby rozpocząć samowykluczenie, skontaktuj się z pomocą techniczną i poinformuj ich, jak długo chcesz zostać wykluczony. Należy pamiętać, że proces ten jest nieodwracalny przez wybrany okres czasu. NetEnt, Microgaming, Pragmatic Play, Habanero, Betsoft, Novomatic powered by ODIBETS It doesn’t wanted KYC verification plus it will give you an anonymous, transparent gambling experience. Golf also offers year-round gaming options, so it’s a famous athletics to own unknown gaming having Bitcoin and you can casino joy review other cryptocurrencies. Significant tournaments for example Wimbledon, the us Discover, the brand new French Unlock, and also the Australian Discover focus significant focus. A knowledgeable platforms are designed for the representative in mind, offering user-friendly interfaces, prompt load moments, and simple routing.

  14. I don’t think the title of your article matches the content lol. Just kidding, mainly because I had some doubts after reading the article.

  15. 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?

  16. Very nice post. I just stumbled upon your blog and wanted to say that I’ve really enjoyed browsing your blog posts. In any case I’ll be subscribing to your feed and I hope you write again soon!

Leave a Reply

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

Back To Top