fred-ye / summary

my blog
43 stars 9 forks source link

Android图片选择器-2 #62

Open fred-ye opened 5 years ago

fred-ye commented 5 years ago

再来看一下图片选择器相关的UI开发,顺便梳理一下UI相关的知识点。常用的一些基础东西就不写了,这里只记录一些容易弄混,出错,不容易记住的东西

假设我们想要做成的图片选择器的UI类似于下图这样: WX20180918-215348@2x.png

主题

我们希望UI上的元素尽可能的可配置,因此将相关的UI属性抽出来并定义成主题。比如:标题的颜色,文字的颜色,字体等。在开发的时候,还是像知乎那样提供两套基础的主题。一种主题的样式显示效果如上图所示,采用白底的头部。另一种,我们采用蓝底的头部。先回顾一下StyleTheme 相关的基础。新增StyleTheme的方式是一样的,都是直接在xml文件的<resources>结点中添加<style>结点,定义名称,添加属性。那么StyleTheme的区别是什么, Google官方是这么说的:

What's the difference between a style and a theme?

Any style can be used as a theme. For example, you could apply the CodeFont style as a theme for an Activity, and all the text inside the Activity would use gray monospace font.

概括来讲,最大的区别是在于Theme是可以用在Activity或整个app。其实有些属性是只能定义在Theme中的,比如windowActionBar属性,就只能作为Theme的属性 当我们在android studio中新建一个module时,工具会自动帮我们生成一个style, 代码如下:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="AppTheme.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />

    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />

</resources>

默认会创建一个主题,即Apptheme,它继承自Theme.AppCompat.Light.DarkActionBar, 然后自定义了三个属性, colorPrimary, colorPrimaryDark, colorAccent。这三个属性是用来定义应用的基础色调。

android系统提供的属性,可以从这里查看。接下来定义自己的Theme

第一步 我们在attr.xml中定义相关的属性 相关的属性后面可以结合着代码看

<resources>
    <attr name="capture.textColor" format="reference|color"/>
    <attr name="item.placeholder" format="reference|color"/>
    <attr name="title.background" format="reference|color"/>
    <attr name="title.commonText.color" format="reference|color"/>
    <attr name="title.actionText.color" format="reference|color"/>
    <attr name="title.arrowDown" format="reference"/>
    <attr name="title.arrowUp" format="reference"/>
    <attr name="title.statusBar.color" format="color"/>
    <attr name="title.statusBar.darkFont" format="boolean"/>
    <attr name="previewTitle.background" format="reference|color"/>
    <attr name="previewTitle.commonText.color" format="reference|color"/>
    <attr name="previewTitle.actionText.color" format="reference|color"/>
</resources>

第二步styles.xml中自定义Theme

 <style name="CommonTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="item.placeholder">@color/item_placeholder</item>
    <item name="capture.textColor">@color/capture_text_color</item>
    <item name="title.background">@color/white</item>
    <item name="title.commonText.color">@color/common_text</item>
    <item name="title.actionText.color">@color/blue_1</item>
    <item name="title.arrowDown">@drawable/arrow_down_black</item>
    <item name="title.arrowUp">@drawable/arrow_up_black</item>
    <item name="title.statusBar.color">@color/white</item>
    <item name="title.statusBar.darkFont">false</item>
    <item name="previewTitle.background">@android:color/black</item>
    <item name="previewTitle.commonText.color">@android:color/white</item>
    <item name="previewTitle.actionText.color">@color/blue_1</item>
</style>

<style name="QQTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="item.placeholder">@color/item_placeholder</item>
    <item name="capture.textColor">@color/white</item>
    <item name="title.background">@color/blue_1</item>
    <item name="title.commonText.color">@color/white</item>
    <item name="title.actionText.color">@color/white</item>
    <item name="title.arrowDown">@drawable/arrow_down_white</item>
    <item name="title.arrowUp">@drawable/arrow_up_white</item>
    <item name="title.statusBar.color">@color/blue_1</item>
    <item name="title.statusBar.darkFont">true</item>
    <item name="previewTitle.background">@android:color/black</item>
    <item name="previewTitle.commonText.color">@android:color/white</item>
    <item name="previewTitle.actionText.color">@color/blue_1</item>
</style>

第三步 在布局文件中使用新定义的属性:

<TextView
    android:id="@+id/selected_album"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:gravity="center"
    android:singleLine="true"
    android:textColor="?title.commonText.color"
    android:text="全部"
    android:drawableRight="?title.arrowDown"
    android:drawablePadding="6dp"
    android:textSize="@dimen/title"
    />

第四步 为Activity或者app指定主题:

<activity android:name=".ui.ImagePreviewActivity" android:theme="@style/CommonTheme"></activity>

沉浸式模式

我们希望用户在使用图片选择器时,进行预览操作, 查看大图时,页面能够隐藏掉状态栏和标题,扩大可视区域。因此需要用到沉浸式模式。

沉浸式模式,也通常有人说是沉浸式导航,immersive 等,但描述的都是同一个东西。沉浸式表现的效果通常有以下种:

来看个Demo, 创建一个Activity, 在其布局文件中

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@mipmap/bg"
        android:scaleType="centerCrop"/>
</LinearLayout>

隐藏掉状态栏

隐藏状态栏,我们只需要加对DecorView调用setSystemUiVisibility 方法,设置flag为View.SYSTEM_UI_FLAG_FULLSCREEN。同时隐藏掉actionBar。代码如下:

View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN);
ActionBar actionBar = getSupportActionBar();
actionBar.hide();

状态栏透明

让状态栏透明,同时让内容延伸到状态栏的下方。代码如下:

if (Build.VERSION.SDK_INT >= 21) {
    View decorView = getWindow().getDecorView();
    int flag = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
    decorView.setSystemUiVisibility(flag);
    getWindow().setStatusBarColor(Color.TRANSPARENT);
}

ActionBar actionBar = getSupportActionBar();
actionBar.hide();

在Android 5.0以后,系统提供了setStatusBarColor方法来手动设置statusBar的背景色,此处我们也仅考虑5.0以上的兼容。需要注意的是此处的flag需要用到SYSTEM_UI_FLAG_LAYOUT_FULLSCREENSYSTEM_UI_FLAG_LAYOUT_STABLE这两个。这样才能保证状态栏显示,同时内容也能延伸到状态栏的下方。

隐藏底部的导航栏

隐藏底部的导航栏操作一般用得少,通常在视频,游戏类app中要多一些。而且隐藏导航栏的操作通常会有些问题,比如,我们用View.SYSTEM_UI_FLAG_HIDE_NAVIGATIONView.SYSTEM_UI_FLAG_FULLSCREEN两个flag, 结果发现,状态栏和导航栏都隐藏了,但是点击一下屏幕又都显示了。只能做一个透明的导航栏,代码如下:

if (Build.VERSION.SDK_INT >= 21) {
    View decorView = getWindow().getDecorView();
    int flag = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
            |View.SYSTEM_UI_FLAG_HIDE_NAVIGATION ;
    decorView.setSystemUiVisibility(flag);
    getWindow().setNavigationBarColor(Color.TRANSPARENT);
    getWindow().setStatusBarColor(Color.TRANSPARENT);
}
ActionBar actionBar = getSupportActionBar();
actionBar.hide();

视频app中全屏的显示

需要重写onWindowFocusChanged方法

public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus && Build.VERSION.SDK_INT >= 19) {
            View decorView = getWindow().getDecorView();
            decorView.setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_FULLSCREEN
                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
        }
    }

以上只是沉浸式最常用的操作,在国内android生态碎片化严重的情况下,会出现兼容性的问题,这篇文章中有部份记载,可以参考。 在项目中我引入了ImmersionBar来管理statusBar,compile com.gyf.barlibrary:barlibrary:2.3.0, 如果想详细了解相关的操作可以去看源码。

列表的显示

在上方的图中,我们可以看到,有两个列表,一个是相册列表,另一个是图片列表。同时,我们也知道,对于机册的图片的查询,都可以通过利用CursorLoader来从Content Provider中查,查询到的数据都以Cursor的形式返回,考虑到这点共性,我们可以将相册和图片列表要使用到的Adapter进行抽象,抽出其共性。我们定义一个父Adapter, 考虑到平时很少将ListViewCursor一起用,回顾一下: 对于ListView, 使用adapter可以屏蔽视图,数据的差异性,让ListView只做好自己份内的事就可以。对于ListView, android 提供了多种类别的Adapter供我们使用,如ArrayAdapter, CursorAdapter, SimpleAdapter等。这三个Adapter都是直接继承自BaseAdapter,其中的CursorAdapter是利用游标来返回数据,在需要从数据库中读取数据或者从Content Provider中读取数据时,使用会比较方便。使用CursorAdapter时,需要重写newViewbindView两个方法, 此处写个Demo回顾一下:

public class ImageCursorAdapter extends CursorAdapter {
    private LayoutInflater cursorInflater;

    public ImageCursorAdapter(Context context, Cursor c) {
        super(context, c);
        cursorInflater = (LayoutInflater) context.getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public int getCount() {
        return super.getCount();
    }

    @Override
    public Object getItem(int position) {
        return super.getItem(position);
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        return cursorInflater.inflate(R.layout.image_list_item, parent, false);
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        ImageView imageView = (ImageView)view.findViewById(R.id.iv_image);
        String imageUrl = cursor.getString( cursor.getColumnIndex( MediaStore.MediaColumns.DATA) );
        Glide.with(context).load(Uri.fromFile(new File(imageUrl))).asBitmap()
                .centerCrop().into(imageView);
    }
}

如果我们使用RecyclerView,都是直接使用RecyclerView中的Adapter类,就不像ListView那样,可以使用CursorAdapter,但我们可以将Cursor传递到RecyclerView.Adapter中去。