适配_适配器 - CSDN
精华内容
参与话题
  • web前端页面适配方法

    万次阅读 2018-10-23 09:09:21
    流式布局:就是百分比布局,非固定像素,内容向两侧填充,...适配要求: 1. 网页宽度必须和浏览器保持一致 2. 默认显示的缩放比例和PC端保持(缩放比例1.0) 3. 不允许用户自行缩放网页 满足这些要求达到了适...

    流式布局:就是百分比布局,非固定像素,内容向两侧填充,理解成流动的布局,称为流式布局

    视觉窗口:viewport,是移动端特有。这是一个虚拟的区域,承载网页的。

    承载关系:浏览器---->viewport---->网页

    适配要求:

    1. 网页宽度必须和浏览器保持一致

    2. 默认显示的缩放比例和PC端保持(缩放比例1.0)

    3. 不允许用户自行缩放网页

    满足这些要求达到了适配,国际上通用的适配方案,标准的移动端适配方案。

    适配设置:

    如果任何设置都没有,默认走的就是viewport的默认设置

    去设置新的viewport设置,达到适配要求。

    <meta name="viewport"> 设置视口的标签 在head里面并且应该紧接着编码设置

    viewport的功能:

    1. width 可以设置宽度 (device-width 当前设备的宽度)

    2. height 可以设置高度

    3. initial-scale 可以设置默认的缩放比例

    4. user-scalable 可以设置是否允许用户自行缩放

    5. maximum-scale 可以设置最大缩放比例

    6. minimum-scale 可以设置最小缩放比例

    在<meta name="viewport" content="" > content="" 使用以上参数

    1. width=device-width 宽度一致比例是1.0

    2. initial-scale=1.0 宽度一致比例是1.0

    3. user-scalable=no 不允许用户自行缩放 (yes,no 1,0)

    标准适配方案:

    <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=0">

    meta:vp + tab 快捷方式

     

    展开全文
  • Android 屏幕适配方案

    万次阅读 多人点赞 2015-08-01 13:20:53
    1、概述大家在Android开发时,肯定会觉得屏幕适配是个尤其痛苦的事,各种屏幕尺寸适配起来蛋疼无比。如果我们换个角度我们看下这个问题,不知道大家有没有了解过web前端开发,或者说大家对于网页都不陌生吧,其实...

    转载请标明出处:
    http://blog.csdn.net/lmj623565791/article/details/45460089
    本文出自:【张鸿洋的博客】

    1、概述

    大家在Android开发时,肯定会觉得屏幕适配是个尤其痛苦的事,各种屏幕尺寸适配起来蛋疼无比。如果我们换个角度我们看下这个问题,不知道大家有没有了解过web前端开发,或者说大家对于网页都不陌生吧,其实适配的问题在web页面的设计中理论上也存在,为什么这么说呢?电脑的显示器的分辨率、包括手机分辨率,我敢说分辨率的种类远超过Android设备的分辨率,那么有一个很奇怪的现象:

    为什么Web页面设计人员从来没有说过,尼玛适配好麻烦?

    那么,到底是什么原因,让网页的设计可以在千差万别的分辨率的分辨率中依旧能给用户一个优质的体验呢?带着这个疑惑,我问了下媳妇(前端人员),媳妇睁大眼睛问我:什么叫适配?fc,尼玛,看来的确没有这类问题。后来再我仔细的追问后,她告诉我,噢,这个尺寸呀,我都是设置为20%的~~追根到底,其实就是一个原因,网页提供了百分比计算大小。

    同样的,大家拿到UI给的设计图以后,是不是抱怨过尼玛你标识的都是px,我项目里面用dp,这什么玩意,和UI人员解释,UI妹妹也不理解。那么本例同样可以解决Android工程师和UI妹妹间的矛盾~UI给出一个固定尺寸的设计稿,然后你在编写布局的时候不用思考,无脑照抄上面标识的像素值,就能达到完美适配,理想丰不丰满~~。

    然而,Android对于不同的屏幕给出的适配方案是dp,那么dp与百分比的差距到底在哪里?

    2、dp vs 百分比

    • dp

    我们首先看下dp的定义:

    Density-independent pixel (dp)独立像素密度。标准是160dip.即1dp对应1个pixel,计算公式如:px = dp * (dpi / 160),屏幕密度越大,1dp对应 的像素点越多。
    上面的公式中有个dpi,dpi为DPI是Dots Per Inch(每英寸所打印的点数),也就是当设备的dpi为160的时候1px=1dp;

    好了,上述这些概念记不记得住没关系,只要记住一点dp是与像素无关的,在实际使用中1dp大约等于1/160inch。

    那么dp究竟解决了适配上的什么问题?可以看出1dp = 1/160inch;那么它至少能解决一个问题,就是你在布局文件写某个View的宽和高为160dp*160dp,这个View在任何分辨率的屏幕中,显示的尺寸大小是大约是一致的(可能不精确),大概是 1 inch * 1 inch。

    但是,这样并不能够解决所有的适配问题:

    • 呈现效果仍旧会有差异,仅仅是相近而已
    • 当设备的物理尺寸存在差异的时候,dp就显得无能为力了。为4.3寸屏幕准备的UI,运行在5.0寸的屏幕上,很可能在右侧和下侧存在大量的空白。而5.0寸的UI运行到4.3寸的设备上,很可能显示不下。

    以上两点,来自参考链接1

    一句话,总结下,dp能够让同一数值在不同的分辨率展示出大致相同的尺寸大小。但是当设备的尺寸差异较大的时候,就无能为力了。适配的问题还需要我们自己去做,于是我们可能会这么做:

    <?xml version="1.0" encoding="utf-8"?>  
    <resources>  
        <!-- values-hdpi 480X800 -->  
        <dimen name="imagewidth">120dip</dimen>      
    </resources>  
    
    <resources>  
        <!-- values-hdpi-1280x800 -->  
        <dimen name="imagewidth">220dip</dimen>      
    </resources>  
    
    
    <?xml version="1.0" encoding="utf-8"?>  
    <resources>  
        <!-- values-hdpi  480X320 -->  
        <dimen name="imagewidth">80dip</dimen>      
    </resources> 

    上述代码片段来自网络,也就是说,我们为了优质的用户体验,依然需要去针对不同的dpi设置,编写多套数值文件。

    可以看出,dp并没有能解决适配问题。下面看百分比。

    • 百分比
      这个概念不用说了,web中支持控件的宽度可以去参考父控件的宽度去设置百分比,最外层控件的宽度参考屏幕尺寸设置百分比,那么其实中Android设备中,只需要支持控件能够参考屏幕的百分比去计算宽高就足够了。

    比如,我现在以下几个需求:

    • 对于图片展示的Banner,为了起到该有的效果,我希望在任何手机上显示的高度为屏幕高度的1/4
    • 我的首页分上下两栏,我希望每个栏目的屏幕高度为11/24,中间间隔为1/12
    • slidingmenu的宽度为屏幕宽度的80%

    当然了这仅仅是从一个大的层面上来说,其实小范围布局,可能百分比将会更加有用。

    那么现在不支持百分比,实现上述的需求,可能需要1、代码去动态计算(很多人直接pass了,太麻烦);2、利用weight(weight必须依赖Linearlayout,而且并不能适用于任何场景)

    再比如:我的某个浮动按钮的高度和宽度希望是屏幕高度的1/12,我的某个Button的宽度希望是屏幕宽度的1/3。

    上述的所有的需求,利用dp是无法完成的,我们希望控件的尺寸可以按照下列方式编写:

       <Button
            android:text="@string/hello_world"
            android:layout_width="20%w"
            android:layout_height="10%h"/>

    利用屏幕的宽和高的比例去定义View的宽和高。

    好了,到此我们可以看到dp与百分比的区别,而百分比能够更好的解决我们的适配问题。

    • some 适配tips

    我们再来看看一些适配的tips

    1. 多用match_parent
    2. 多用weight
    3. 自定义view解决

    其实上述3点tip,归根结底还是利用百分比,match_parent相当于100%参考父控件;weight即按比例分配;自定义view无非是因为里面多数尺寸是按照百分比计算的;

    通过这些tips,我们更加的看出如果能在Android中引入百分比的机制,将能解决大多数的适配问题,下面我们就来看看如何能够让Android支持百分比的概念。

    3、百分比的引入

    1、引入

    其实我们的解决方案,就是在项目中针对你所需要适配的手机屏幕的分辨率各自简历一个文件夹。

    如下图:

    20150503174449732

    然后我们根据一个基准,为基准的意思就是:

    比如480*320的分辨率为基准

    • 宽度为320,将任何分辨率的宽度分为320份,取值为x1-x320
    • 高度为480,将任何分辨率的高度分为480份,取值为y1-y480

    例如对于800*480的宽度480:

    20150503180400049

    可以看到x1 = 480 / 基准 = 480 / 320 = 1.5 ;

    其他分辨率类似~~
    你可能会问,这么多文件,难道我们要手算,然后自己编写?不要怕,下文会说。

    那么,你可能有个疑问,这么写有什么好处呢?

    假设我现在需要在屏幕中心有个按钮,宽度和高度为我们屏幕宽度的1/2,我可以怎么编写布局文件呢?

    <FrameLayout >
    
        <Button
            android:layout_gravity="center"
            android:gravity="center"
            android:text="@string/hello_world"
            android:layout_width="@dimen/x160"
            android:layout_height="@dimen/x160"/>
    
    </FrameLayout>
    

    可以看到我们的宽度和高度定义为x160,其实就是宽度的50%;
    那么效果图:

    20150503180542414

    可以看到不论在什么分辨率的机型,我们的按钮的宽和高始终是屏幕宽度的一半。

    • 对于设计图

    假设现在的UI的设计图是按照480*320设计的,且上面的宽和高的标识都是px的值,你可以直接将px转化为x[1-320],y[1-480],这样写出的布局基本就可以全分辨率适配了。

    你可能会问:设计师设计图的分辨率不固定怎么办?下文会说~

    • 对于上文提出的几个dp做不到的

    你可以通过在引入百分比后,自己试试~~

    好了,有个最主要的问题,我们没有说,就是分辨率这么多,尼玛难道我们要自己计算,然后手写?

    2、自动生成工具

    好了,其实这样的文件夹手写也可以,按照你们需要支持的分辨率,然后编写一套,以后一直使用。

    当然了,作为程序员的我们,怎么能做这么low的工作,肯定要程序来实现:

    那么实现需要以下步骤:

    1. 分析需要的支持的分辨率

    对于主流的分辨率我已经集成到了我们的程序中,当然对于特殊的,你可以通过参数指定。关于屏幕分辨率信息,可以通过该网站查询:http://screensiz.es/phone

    1. 编写自动生成文件的程序

    代码如下

    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.PrintWriter;
    
    /**
     * Created by zhy on 15/5/3.
     */
    public class GenerateValueFiles {
    
        private int baseW;
        private int baseH;
    
        private String dirStr = "./res";
    
        private final static String WTemplate = "<dimen name=\"x{0}\">{1}px</dimen>\n";
        private final static String HTemplate = "<dimen name=\"y{0}\">{1}px</dimen>\n";
    
        /**
         * {0}-HEIGHT
         */
        private final static String VALUE_TEMPLATE = "values-{0}x{1}";
    
        private static final String SUPPORT_DIMESION = "320,480;480,800;480,854;540,960;600,1024;720,1184;720,1196;720,1280;768,1024;800,1280;1080,1812;1080,1920;1440,2560;";
    
        private String supportStr = SUPPORT_DIMESION;
    
        public GenerateValueFiles(int baseX, int baseY, String supportStr) {
            this.baseW = baseX;
            this.baseH = baseY;
    
            if (!this.supportStr.contains(baseX + "," + baseY)) {
                this.supportStr += baseX + "," + baseY + ";";
            }
    
            this.supportStr += validateInput(supportStr);
    
            System.out.println(supportStr);
    
            File dir = new File(dirStr);
            if (!dir.exists()) {
                dir.mkdir();
    
            }
            System.out.println(dir.getAbsoluteFile());
    
        }
    
        /**
         * @param supportStr
         *            w,h_...w,h;
         * @return
         */
        private String validateInput(String supportStr) {
            StringBuffer sb = new StringBuffer();
            String[] vals = supportStr.split("_");
            int w = -1;
            int h = -1;
            String[] wh;
            for (String val : vals) {
                try {
                    if (val == null || val.trim().length() == 0)
                        continue;
    
                    wh = val.split(",");
                    w = Integer.parseInt(wh[0]);
                    h = Integer.parseInt(wh[1]);
                } catch (Exception e) {
                    System.out.println("skip invalidate params : w,h = " + val);
                    continue;
                }
                sb.append(w + "," + h + ";");
            }
    
            return sb.toString();
        }
    
        public void generate() {
            String[] vals = supportStr.split(";");
            for (String val : vals) {
                String[] wh = val.split(",");
                generateXmlFile(Integer.parseInt(wh[0]), Integer.parseInt(wh[1]));
            }
    
        }
    
        private void generateXmlFile(int w, int h) {
    
            StringBuffer sbForWidth = new StringBuffer();
            sbForWidth.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
            sbForWidth.append("<resources>");
            float cellw = w * 1.0f / baseW;
    
            System.out.println("width : " + w + "," + baseW + "," + cellw);
            for (int i = 1; i < baseW; i++) {
                sbForWidth.append(WTemplate.replace("{0}", i + "").replace("{1}",
                        change(cellw * i) + ""));
            }
            sbForWidth.append(WTemplate.replace("{0}", baseW + "").replace("{1}",
                    w + ""));
            sbForWidth.append("</resources>");
    
            StringBuffer sbForHeight = new StringBuffer();
            sbForHeight.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
            sbForHeight.append("<resources>");
            float cellh = h *1.0f/ baseH;
            System.out.println("height : "+ h + "," + baseH + "," + cellh);
            for (int i = 1; i < baseH; i++) {
                sbForHeight.append(HTemplate.replace("{0}", i + "").replace("{1}",
                        change(cellh * i) + ""));
            }
            sbForHeight.append(HTemplate.replace("{0}", baseH + "").replace("{1}",
                    h + ""));
            sbForHeight.append("</resources>");
    
            File fileDir = new File(dirStr + File.separator
                    + VALUE_TEMPLATE.replace("{0}", h + "")//
                            .replace("{1}", w + ""));
            fileDir.mkdir();
    
            File layxFile = new File(fileDir.getAbsolutePath(), "lay_x.xml");
            File layyFile = new File(fileDir.getAbsolutePath(), "lay_y.xml");
            try {
                PrintWriter pw = new PrintWriter(new FileOutputStream(layxFile));
                pw.print(sbForWidth.toString());
                pw.close();
                pw = new PrintWriter(new FileOutputStream(layyFile));
                pw.print(sbForHeight.toString());
                pw.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    
        public static float change(float a) {
            int temp = (int) (a * 100);
            return temp / 100f;
        }
    
        public static void main(String[] args) {
            int baseW = 320;
            int baseH = 400;
            String addition = "";
            try {
                if (args.length >= 3) {
                    baseW = Integer.parseInt(args[0]);
                    baseH = Integer.parseInt(args[1]);
                    addition = args[2];
                } else if (args.length >= 2) {
                    baseW = Integer.parseInt(args[0]);
                    baseH = Integer.parseInt(args[1]);
                } else if (args.length >= 1) {
                    addition = args[0];
                }
            } catch (NumberFormatException e) {
    
                System.err
                        .println("right input params : java -jar xxx.jar width height w,h_w,h_..._w,h;");
                e.printStackTrace();
                System.exit(-1);
            }
    
            new GenerateValueFiles(baseW, baseH, addition).generate();
        }
    
    }

    同时我提供了jar包,默认情况下,双击即可生成,使用说明:

    20150503173911632

    下载地址见文末,内置了常用的分辨率,默认基准为480*320,当然对于特殊需求,通过命令行指定即可:

    例如:基准 1280 * 800 ,额外支持尺寸:1152 * 735;4500 * 3200;

    20150503173719049

    按照

    java -jar xx.jar width height width,height_width,height

    上述格式即可。

    到此,我们通过编写一个工具,根据某基准尺寸,生成所有需要适配分辨率的values文件,做到了编写布局文件时,可以参考屏幕的分辨率;在UI给出的设计图,可以快速的按照其标识的px单位进行编写布局。基本解决了适配的问题。

    本方案思想已经有公司投入使用,个人认为还是很不错的,如果大家有更好的方案来解决屏幕适配的问题,欢迎留言探讨或者直接贴出好文链接,大家可以将自己的经验进行分享,这样才能壮大我们的队伍~~。

    注:本方案思想来自Android Day Day Up 一群的【blue-深圳】,经其同意编写此文,上述程序也很大程度上借鉴了其分享的源码。在此标识感谢,预祝其创业成功!

    ===>后期更新

    Google已经添加了百分比支持库,详情请看:Android 百分比布局库(percent-support-lib) 解析与扩展

    ok~

    群号:463081660,欢迎入群

    下载地址

    微信公众号:hongyangAndroid
    (欢迎关注,第一时间推送博文信息)
    1422600516_2905.jpg

    参考链接

    Android多屏幕适配学习笔记

    开源,原创,实用Android 屏幕适配方案分享

    展开全文
  • Android 10适配要点,作用域存储

    万次阅读 多人点赞 2020-09-03 15:07:27
    距离Android 10系统正式发布已经过去大半年左右的时间了,你的应用程序已经对它进行适配了吗?在Android 10众多的行为变更当中,有一点是非常值得引起我们重视的,那就是作用域存储。这个新功能直接颠覆了长久以来...

    本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每个工作日都有文章更新。

    距离Android 10系统正式发布已经过去大半年左右的时间了,你的应用程序已经对它进行适配了吗?

    在Android 10众多的行为变更当中,有一点是非常值得引起我们重视的,那就是作用域存储。这个新功能直接颠覆了长久以来我们一直惯用的外置存储空间的使用方式,因此大量App都将面临着较多代码模块的升级。

    然而,对于作用域存储这个新功能,官方的资料并不多,很多人也没有搞明白它的用法。另外它也不属于《第一行代码》现有的知识架构体系,虽然我有想过在第3版中加入这部分内容的讲解,但几经思考之后还是决定以一讲单独文章的方式来讲解这部分内容,也算是作为《第一行代码 第3版》的内容扩展吧。

    本篇文章对作用域存储进行了比较全面的解析,相信看完之后你将能够轻松地完成Android 10作用域存储的适配升级。

    理解作用域存储

    Android长久以来都支持外置存储空间这个功能,也就是我们常说的SD卡存储。这个功能使用得极其广泛,几乎所有的App都喜欢在SD卡的根目录下建立一个自己专属的目录,用来存放各类文件和数据。

    那么这么做有什么好处吗?我想了一下,大概有两点吧。第一,存储在SD卡的文件不会计入到应用程序的占用空间当中,也就是说即使你在SD卡存放了1G的文件,你的应用程序在设置中显示的占用空间仍然可能只有几十K。第二,存储在SD卡的文件,即使应用程序被卸载了,这些文件仍然会被保留下来,这有助于实现一些需要数据被永久保留的功能。

    然而,这些“好处”真的是好处吗?或 许对于开发者而言这算是好处吧,但对于用户而言,上述好处无异于一些流氓行为。因为这会将用户的SD卡空间搞得乱糟糟的,而且即使我卸载了一个完全不再使用的程序,它所产生的垃圾文件却可能会一直保留在我的手机上。

    另外,存储在SD卡上的文件属于公有文件,所有的应用程序都有权随意访问,这也对数据的安全性带来了很大的挑战。

    为了解决上述问题,Google在Android 10当中加入了作用域存储功能。

    那么到底什么是作用域存储呢?简单来讲,就是Android系统对SD卡的使用做了很大的限制。从Android 10开始,每个应用程序只能有权在自己的外置存储空间关联目录下读取和创建文件,获取该关联目录的代码是:context.getExternalFilesDir()。关联目录对应的路径大致如下:

    /storage/emulated/0/Android/data/<包名>/files
    

    将数据存放到这个目录下,你将可以完全使用之前的写法来对文件进行读写,不需要做任何变更和适配。但同时,刚才提到的那两个“好处”也就不存在了。这个目录中的文件会被计入到应用程序的占用空间当中,同时也会随着应用程序的卸载而被删除。

    那么有些朋友可能会问了,我就是需要访问其他目录该怎么办呢?比如读取手机相册中的图片,或者向手机相册中添加一张图片。为此,Android系统针对文件类型进行了分类,图片、音频、视频这三类文件将可以通过MediaStore API来进行访问,而其他类型的文件则需要使用系统的文件选择器来进行访问。

    另外,我们的应用程序向媒体库贡献的图片、音频或视频,将会自动拥有其读写权限,不需要额外申请READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限。而如果你要读取其他应用程序向媒体库贡献的图片、音频或视频,则必须要申请READ_EXTERNAL_STORAGE权限才行。WRITE_EXTERNAL_STORAGE权限将会在未来的Android版本中废弃。

    好了,关于作用域存储的理论知识就先讲到这里,相信你已经对它有了一个基本的了解了,那么接下来我们就开始上手操作吧。

    我一定要升级吗?

    一定会有很多朋友关心这个问题,因为每当适配升级面临着需要更改大量代码的时候,大多数人的第一想法都是能不升就不升,或者能晚升就晚升。而在作用域存储这个功能上面,恭喜大家,暂时确实是可以不用升级的。

    目前Android 10系统对于作用域存储适配的要求还不是那么严格,毕竟之前传统外置存储空间的用法实在是太广泛了。如果你的项目指定的targetSdkVersion低于29,那么即使不做任何作用域存储方面的适配,你的项目也可以成功运行到Android 10手机上。

    而如果你的targetSdkVersion已经指定成了29,也没有关系,假如你还不想进行作用域存储的适配,只需要在AndroidManifest.xml中加入如下配置即可:

    <manifest ... >
      <application android:requestLegacyExternalStorage="true" ...>
        ...
      </application>
    </manifest>
    

    这段配置表示,即使在Android 10系统上,仍然允许使用之前遗留的外置存储空间的用法来运行程序,这样就不用对代码进行任何修改了。当然,这只是一种权宜之计,在未来的Android系统版本中,这段配置随时都可能会失效(Android 11中已强制启用作用域存储,这段配置在Android 11当中已不再有效)。因此,我们还是非常有必要现在就来学习一下,到底该如何对作用域存储进行适配。

    另外,本篇文章中演示的所有示例,都可以到ScopedStorageDemo这个开源库中找到其对应的源码。

    开源库地址是:https://github.com/guolindev/ScopedStorageDemo

    获取相册中的图片

    首先来学习一下如何在作用域存储当中获取手机相册里的图片。注意,虽然本篇文章中我是以图片来举例的,但是获取音频、视频的用法也是基本相同的。

    不同于过去可以直接获取到相册中图片的绝对路径,在作用域存储当中,我们只能借助MediaStore API获取到图片的Uri,示例代码如下:

    val cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, "${MediaStore.MediaColumns.DATE_ADDED} desc")
    if (cursor != null) {
        while (cursor.moveToNext()) {
            val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
            val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
            println("image uri is $uri")
        }
    	cursor.close()
    }
    

    上述代码中,我们先是通过ContentResolver获取到了相册中所有图片的id,然后再借助ContentUris将id拼装成一个完整的Uri对象。一张图片的Uri格式大致如下所示:

    content://media/external/images/media/321
    

    那么有些朋友可能会问了,获取到了Uri之后,我又该怎样将这张图片显示出来呢?这就有很多种办法了,比如使用Glide来加载图片,它本身就支持传入Uri对象来作为图片路径:

    Glide.with(context).load(uri).into(imageView)
    

    而如果你没有使用Glide或其他图片加载框架,想在不借助第三方库的情况下直接将一个Uri对象解析成图片,可以使用如下代码:

    val fd = contentResolver.openFileDescriptor(uri, "r")
    if (fd != null) {
        val bitmap = BitmapFactory.decodeFileDescriptor(fd.fileDescriptor)
    	fd.close()
        imageView.setImageBitmap(bitmap)
    }
    

    上述代码中,我们调用了ContentResolver的openFileDescriptor()方法,并传入Uri对象来打开文件句柄,然后再调用BitmapFactory的decodeFileDescriptor()方法将文件句柄解析成Bitmap对象即可。

    Demo效果:

    这样我们就将获取相册中图片的方式掌握了,并且这种方式在所有的Android系统版本中都适用。

    那么接下来,我们开始学习如何将一张图片添加到相册。

    将图片添加到相册

    将一张图片添加到手机相册要相对稍微复杂一点,因为不同系统版本之间的处理方式是不太一样的。

    我们还是通过一段代码示例来直观地学习一下,代码如下所示:

    fun addBitmapToAlbum(bitmap: Bitmap, displayName: String, mimeType: String, compressFormat: Bitmap.CompressFormat) {
        val values = ContentValues()
        values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
        values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
        } else {
            values.put(MediaStore.MediaColumns.DATA, "${Environment.getExternalStorageDirectory().path}/${Environment.DIRECTORY_DCIM}/$displayName")
        }
        val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        if (uri != null) {
            val outputStream = contentResolver.openOutputStream(uri)
            if (outputStream != null) {
                bitmap.compress(compressFormat, 100, outputStream)
    			outputStream.close()
            }
        }
    }
    

    这段代码演示了如何将一个Bitmap对象添加到手机相册当中,我来简单解释一下。

    想要将一张图片添加到手机相册,我们需要构建一个ContentValues对象,然后向这个对象中添加三个重要的数据。一个是DISPLAY_NAME,也就是图片显示的名称,一个是MIME_TYPE,也就是图片的mime类型。还有一个是图片存储的路径,不过这个值在Android 10和之前的系统版本中的处理方式不一样。Android 10中新增了一个RELATIVE_PATH常量,表示文件存储的相对路径,可选值有DIRECTORY_DCIM、DIRECTORY_PICTURES、DIRECTORY_MOVIES、DIRECTORY_MUSIC等,分别表示相册、图片、电影、音乐等目录。而在之前的系统版本中并没有RELATIVE_PATH,所以我们要使用DATA常量(已在Android 10中废弃),并拼装出一个文件存储的绝对路径才行。

    有了ContentValues对象之后,接下来调用ContentResolver的insert()方法即可获得插入图片的Uri。但仅仅获得Uri仍然是不够的,我们还需要向该Uri所对应的图片写入数据才行。调用ContentResolver的openOutputStream()方法获得文件的输出流,然后将Bitmap对象写入到该输出流当中即可。

    以上代码即可实现将Bitmap对象存储到手机相册当中,那么有些朋友可能会问了,如果我要存储的图片并不是Bitmap对象,而是一张网络上的图片,或者是当前应用关联目录下的图片该怎么办呢?

    其实方法都是相似的,因为不管是网络上的图片还是关联目录下的图片,我们都能获取到它的输入流,只要不断读取输入流中的数据,然后写入到相册图片所对应的输出流当中就可以了,示例代码如下:

    fun writeInputStreamToAlbum(inputStream: InputStream, displayName: String, mimeType: String) {
        val values = ContentValues()
        values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
        values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
        } else {
            values.put(MediaStore.MediaColumns.DATA, "${Environment.getExternalStorageDirectory().path}/${Environment.DIRECTORY_DCIM}/$displayName")
        }
        val bis = BufferedInputStream(inputStream)
        val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        if (uri != null) {
            val outputStream = contentResolver.openOutputStream(uri)
            if (outputStream != null) {
                val bos = BufferedOutputStream(outputStream)
                val buffer = ByteArray(1024)
                var bytes = bis.read(buffer)
                while (bytes >= 0) {
                    bos.write(buffer, 0 , bytes)
                    bos.flush()
                    bytes = bis.read(buffer)
                }
                bos.close()
            }
        }
        bis.close()
    }
    

    这段代码中只是将输入流和输出流的部分重新编写了一下,其他部分和之前存储Bitmap的代码是完全一致的,相信很好理解。

    Demo效果:

    好了,这样我们就将相册图片的读取和存储问题都解决了,下面我们来探讨另外一个常见的需求,如何将文件下载到Download目录。

    下载文件到Download目录

    执行文件下载操作是一个很常见的场景,比如说下载pdf、doc文件,或者下载APK安装包等等。在过去,这些文件我们通常都会下载到Download目录,这是一个专门用于存放下载文件的目录。而从Android 10开始,我们已经不能以绝对路径的方式访问外置存储空间了,所以文件下载功能也会受到影响。

    那么该如何解决呢?主要有以下两种方式。

    第一种同时也是最简单的一种方式,就是更改文件的下载目录。将文件下载到应用程序的关联目录下,这样不用修改任何代码就可以让程序在Android 10系统上正常工作。但使用这种方式,你需要知道,下载的文件会被计入到应用程序的占用空间当中,同时如果应用程序被卸载了,该文件也会一同被删除。另外,存放在关联目录下的文件只能被当前的应用程序所访问,其他程序是没有读取权限的。

    以上几个限制条件如果不能满足你的需求,那么就只能使用第二种方式,对Android 10系统进行代码适配,仍然将文件下载到Download目录下。

    其实将文件下载到Download目录,和向相册中添加一张图片的过程是差不多的,Android 10在MediaStore中新增了一种Downloads集合,专门用于执行文件下载操作。但由于每个项目下载功能的实现都各不相同,有些项目的下载实现还十分复杂,因此怎么将以下的示例代码融合到你的项目当中是你自己需要思考的问题。

    fun downloadFile(fileUrl: String, fileName: String) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            Toast.makeText(this, "You must use device running Android 10 or higher", Toast.LENGTH_SHORT).show()
            return
        }
        thread {
    		try {
    			val url = URL(fileUrl)
    			val connection = url.openConnection() as HttpURLConnection
    			connection.requestMethod = "GET"
    			connection.connectTimeout = 8000
    			connection.readTimeout = 8000
    			val inputStream = connection.inputStream
    			val bis = BufferedInputStream(inputStream)
    			val values = ContentValues()
    			values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
    			values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
    			val uri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
    			if (uri != null) {
    				val outputStream = contentResolver.openOutputStream(uri)
    				if (outputStream != null) {
    					val bos = BufferedOutputStream(outputStream)
    					val buffer = ByteArray(1024)
    					var bytes = bis.read(buffer)
    					while (bytes >= 0) {
    						bos.write(buffer, 0 , bytes)
    						bos.flush()
    						bytes = bis.read(buffer)
    					}
    					bos.close()
    				}
    			}
    			bis.close()
    		} catch(e: Exception) {
    			e.printStackTrace()
    		}
        }
    }
    

    这段代码总体来讲还是比较好理解的,主要就是添加了一些Http请求的代码,并将MediaStore.Images.Media改成了MediaStore.Downloads,其他部分几乎是没有变化的,我就不再多加解释了。

    注意,上述代码只能在Android 10或更高的系统版本上运行,因为MediaStore.Downloads是Android 10中新增的API。至于Android 9及以下的系统版本,请你仍然使用之前的代码来进行文件下载。

    Demo效果:


    使用文件选择器

    如果我们要读取SD卡上非图片、音频、视频类的文件,比如说打开一个PDF文件,这个时候就不能再使用MediaStore API了,而是要使用文件选择器。

    但是,我们不能再像之前的写法那样,自己写一个文件浏览器,然后从中选取文件,而是必须要使用手机系统中内置的文件选择器。示例代码如下:

    const val PICK_FILE = 1
    
    private fun pickFile() {
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
        intent.addCategory(Intent.CATEGORY_OPENABLE)
        intent.type = "*/*"
        startActivityForResult(intent, PICK_FILE)
    }
    
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
            PICK_FILE -> {
                if (resultCode == Activity.RESULT_OK && data != null) {
                    val uri = data.data
                    if (uri != null) {
                        val inputStream = contentResolver.openInputStream(uri)
    					// 执行文件读取操作
                    }
                }
            }
        }
    }
    

    这里在pickFile()方法当中通过Intent去启动系统的文件选择器,注意Intent的action和category都是固定不变的。而type属性可以用于对文件类型进行过滤,比如指定成image/就可以只显示图片类型的文件,这里写成/*表示显示所有类型的文件。注意type属性必须要指定,否则会产生崩溃。

    然后在onActivityResult()方法当中,我们就可以获取到用户选中文件的Uri,之后通过ContentResolver打开文件输入流来进行读取就可以了。

    Demo效果:


    第三方SDK不支持作用域存储怎么办?

    阅读完了本篇文章之后,相信你对Android 10作用域存储的用法和适配基本上都已经掌握了。然而我们在实际的开发工作当中还可能会面临一个非常头疼的问题,就是我自己的代码当然可以进行适配,但是项目中使用的第三方SDK还不支持作用域存储该怎么办呢?

    这个情况确实是存在的,比如我之前使用的七牛云SDK,它的文件上传功能要求你传入的就是一个文件的绝对路径,而不支持传入Uri对象,大家应该也会碰到类似的问题。

    由于我们是没有权限修改第三方SDK的,因此最简单直接的办法就是等待第三方SDK的提供者对这部分功能进行更新,在那之前我们先不要将targetSdkVersion指定到29,或者先在AndroidManifest文件中配置一下requestLegacyExternalStorage属性。

    然而如果你不想使用这种权宜之计,其实还有一个非常好的办法来解决此问题,就是我们自己编写一个文件复制功能,将Uri对象所对应的文件复制到应用程序的关联目录下,然后再将关联目录下这个文件的绝对路径传递给第三方SDK,这样就可以完美进行适配了。这个功能的示例代码如下:

    fun copyUriToExternalFilesDir(uri: Uri, fileName: String) {
        val inputStream = contentResolver.openInputStream(uri)
        val tempDir = getExternalFilesDir("temp")
        if (inputStream != null && tempDir != null) {
            val file = File("$tempDir/$fileName")
            val fos = FileOutputStream(file)
            val bis = BufferedInputStream(inputStream)
            val bos = BufferedOutputStream(fos)
            val byteArray = ByteArray(1024)
            var bytes = bis.read(byteArray)
            while (bytes > 0) {
                bos.write(byteArray, 0, bytes)
                bos.flush()
                bytes = bis.read(byteArray)
            }
            bos.close()
            fos.close()
        }
    }
    

    好的,关于Android 10作用域存储的重要知识点就讲到这里,相信你已经可以完全掌握了。下篇文章中我们会继续学习Android 10适配,讲一讲深色主题的功能,详见链见: Android 10适配要点,深色主题

    注:本篇文章中演示的所有示例,都可以到ScopedStorageDemo这个开源库中找到其对应的源码。

    开源库地址是:https://github.com/guolindev/ScopedStorageDemo


    本篇文章是《第一行代码 第3版》的配套扩展文章,目前《第一行代码 第3版》已经出版,Kotlin、Jetpack、MVVM,你所关心的知识点都在这里,详情点击这里查看

    京东购买地址

    当当购买地址

    天猫购买地址


    关注我的技术公众号,每天都有优质技术文章推送。

    微信扫一扫下方二维码即可关注:

    展开全文
  • 关于UIViewAutoresizing UIView有一个autoresizingMask属性: @property(nonatomic) UIViewAutoresizing autoresizingMask; // simple resize. default is UIViewAutoresizingNone UIViewAutoresizing是一个option ...

    关于UIViewAutoresizing

    UIView有一个autoresizingMask属性:

    @property(nonatomic) UIViewAutoresizing autoresizingMask;    // simple resize. default is UIViewAutoresizingNone
    

    UIViewAutoresizing是一个option

    typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
        UIViewAutoresizingNone                 = 0,
        UIViewAutoresizingFlexibleLeftMargin   = 1 << 0, 
        UIViewAutoresizingFlexibleWidth        = 1 << 1,
        UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
        UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
        UIViewAutoresizingFlexibleHeight       = 1 << 4,
        UIViewAutoresizingFlexibleBottomMargin = 1 << 5
    };
    

    适配规则

    • UIViewAutoresizingFlexibleLeftMargin

    自动调整view与父视图左侧的距离,也就是说固定了view与父视图右侧的距离

    • UIViewAutoresizingFlexibleWidth

    自动调节view的宽,也就是说固定了view与父视图左侧和右侧的距离

    view会被向两边拉伸,或者向中间收缩

    • UIViewAutoresizingFlexibleRightMargin

    自动调节view与父视图右侧距离,也就是说固定了view与父视图左侧的距离

    • UIViewAutoresizingFlexibleTopMargin

    自动调节view与父视图上边距,也就是说固定了view与父视图下边距的距离

    • UIViewAutoresizingFlexibleHeight

    调节view与父视图的相对宽,也就是说固定了view与父视图的左右侧边距。

    • UIViewAutoresizingFlexibleBottomMargin

    调节view相对父视图的高度,固定了view与父视图上下边距。

    举例

    //固定view和父视图相对的大小

    view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    
    展开全文
  • 在Andorid手机的系统设置中有改变字体大小的功能,如果你没有进行适配的话,字体变大,布局混乱,就会造成尴尬处境 通用解决方案 : sp 转为 dp 属性 改变前 : &amp;lt;TextView android:layout_width=...
  • 前端移动端适配总结

    万次阅读 多人点赞 2018-02-28 10:29:49
    meta标签到底做了什么事情做过移动端适配的小伙伴一定有遇到过这行代码:&lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;但是,很多小伙伴只是感性的...
  • H5移动端适配总结

    千次阅读 2019-06-17 16:28:07
    近段时间以来在做微信公众号的H5开发,然而安装UI设计人员给出的尺寸在不同的移动端设备上的表现并不一致,并且在界面适配方面本以为只要在开发过程中尽量使用百分比和flex布局等就可以实现,然而在不同分辨率的手机...
  • 前端解决移动端适配的五种方法

    千次阅读 2020-05-11 13:42:50
    移动端适配的五种方法 所谓移动端适配,就是WebApp在不同尺寸的屏幕上等比显示 第一种方法:viewport适配 原理:通过设置 initial-scale , 将所有设备布局视口的宽度调整为设计图的宽度. //获取meta节点 var ...
  • 适配不同分辨率屏幕

    万次阅读 2016-06-29 11:25:04
    如今的屏幕分辨率,小至320px(iPhone),大到2560px甚至更高(大显示器),变化范围极大。除了使用传统的台式机,用户会越来越多的通过手机、上网本、iPad一类的平板设备来浏览页面。这种情况下,固定宽度的设计...
  • H5页面适配 iPhoneX,就是这么简单

    万次阅读 2018-04-19 20:28:38
    iPhoneX 取消了物理按键,改成底部小黑条,这一改动导致网页出现了比较尴尬的屏幕适配问题。对于网页而言,顶部(刘海部位)的适配问题浏览器已经做了处理,所以我们只需要关注底部与小黑条的适配问题即可(即常见...
  • iPhone屏幕尺寸、分辨率及适配

    万次阅读 多人点赞 2017-07-29 12:08:57
    从初代iPhone3GS到现如今的...如何适配不同的屏幕尺寸,使UI更加协调美观,这给iPhone/iOS应用开发者带来了挑战。 本文结合个人在iOS UI开发和适配方面的粗浅经验,对常用屏幕适配相关因素做个梳理盘点,以备日后查阅。
  • vue项目移动端适配的方法

    万次阅读 2020-10-13 09:32:51
    1,安装 flexible和 postcss-px2rem(命令行安装) npm install lib-flexible --save npm install postcss-px2rem --save 简要介绍这两个包的用途: 例如在Galaxy S III: 例如在iphone6/7/8: ...
  • android各版本分辨率适配(项目亲测)

    万次阅读 热门讨论 2019-01-05 15:30:58
    1. 写在前面本人项目使用的如下适配方式,已经迭代了几个版本,从开始的各种适配问题,到现在很少有人反馈手机适配问题,自己也在4-5个真机测试没什么问题。这种适配方式的理论支持我最开始是跟着鸿洋大神 一步步操...
  • html页面手机适配方法

    万次阅读 2018-03-29 11:16:44
    在head中添加&lt;meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"&gt; viewport :用户网页的可视...
  • html页面适配手机设定

    万次阅读 2016-12-09 16:32:21
    在html中header头添加以下语句
  • 对于安卓来发开说,最令人头疼的就是适配问题了,三星S8的18.5:9,小米的MIX 17:9 ,全面屏的出现给开发者带来了适配的问题,虽然不影响功能的使用,但是下面的黑条条确实很影响美观,影响用户用户,废话不说了,下面...
  • 移动端屏幕适配(Rem+js方法)

    万次阅读 2018-08-02 21:50:33
    1.什么是移动端适配 在制作webapp时,一个很关键的问题就是适配各种机型不同屏幕的大小,让每种机型上的布局看起来都尽量一样。 也就是说用同一套代码在不同分辨率的手机上跑时,页面元素间的间距、留白,以及图片...
  • android最简单的平板适配方法

    万次阅读 2019-02-22 14:27:19
    其实通过dp这个单位和存放不同图片资源的文件夹(mdpi,xhdpi 等等),理论上安卓已经可以适配大部分的机型了。但是还有一些特殊机型不能适配,但是之前也没要求我们适配所有手机,所以做个两三套的图纸也能完成适配。 ...
  • Unity UGUI的适配方法总结

    万次阅读 2017-09-11 13:40:14
    刚开始研究Unity没多久,对UGUI系统的使用还没有实际开发经验,但是根据之前做cocos产品的适配策略,对UGUI的适配做了研究后发现UGUI的提供的适配方案还是蛮全面的,这里自我总结一下,希望能帮助刚使用UGUI做界面的...
  • 最近在做商城的项目,发现有赞的ui非常适合,但有一个问题是有赞的ui用的单位是px,做不了移动端的适配,官网的提供的vw适配方案发现不可行,最后还是决定rem来做适配,上网搜了一下,发现了一套可用方案,亲测有效...
1 2 3 4 5 ... 20
收藏数 239,282
精华内容 95,712
关键字:

适配