高效的Android布局

Efficient Android Layouts

精心编写的高效布局可以提高性能并简化维护。 在本GOTO哥本哈根讲座中,Daniel Lew介绍了如何为您的应用编写最佳布局。 我们将介绍一系列改进,包括选择正确的ViewGroup,有效利用资源限定符,避免代码重复以及更多提示和技巧。 来吧,学习如何获得每一个视图最大的效率!


查看/下载幻灯片

介绍

我已经做了大约七年的Android 开发,首先在旅游App公司,然后Expedia,现在目前在Trello。 这个演讲是关于高效的Android布局,当我写它,我发现是我真正感兴趣的不是那么多的性能方面的效率,但有作为一个 开发者的抛砖引玉。
我开始考虑阿基米德指的是支点的工作方式,如果你给他一个合适的支点,他可以翘起地球。 这是这次演讲的重点。 如何获得最大的杠杆作为开发人员,因为许多Android团队是相当小,你被要求做很多不同事情。

ViewGroups

让我们谈谈选择哪个ViewGroup用于任何特定的布局。我认为最主要的是,更简单的ViewGroup你可以放弃,更好,因为需要更多复杂的维护,并遇到更多的错误。
在高端的东西,RelativeLayout是最复杂的之一。 (ConstraintLayout看起来可能会比RelativeLayout更复杂,当它终于完成了。)在某处有一个LinearLayout,然后在底部是FrameLayout,这是我最喜欢的,因为它是这么简单。
还有许多其他视图,但这些是大多数应用程序的主要构建块。 RelativeLayout和ConstraintLayout排序在Android中占据相同的空间,现在,这是他们相对于彼此的位置视图。 RelativeLayout是有限的,但它是我们从一开始,而ConstraintLayout是新的,可以做所有这些惊人的事情。
但是,他们都有关键的问题,除了它们是相当复杂的事实。 RelativeLayout很慢,而ConstraintLayout现在是alpha-ish。他们还没有正式释放它。在Maven Central上有alpha构建,但有几次他们完全改变了API,所以它不是生产准备好。
LinearLayout非常适合垂直和水平堆叠视图;你可以分配重量。这是一个简单的视图,其中行被垂直堆叠,然后我也平均分配这两个旋转器之间的权重。
我实际上是嵌套的LinearLayouts,而不是RelativeLayout。是的,LinearLayouts有时很慢。如果你使用布局的重量,你把它们深深地嵌套,那么他们可能会很慢。但这只是一个有时的事情,而RelativeLayout总是要做两个通行证。它总是很慢。希望最终的ConstraintLayout将是我们的救物主,并避免我们不得不在他们两个之间做出决定。
在此期间,我认为最重要的是专注于剖析。无论你最终得到什么布局,打开配置文件GPU渲染,看看你的测试设备上是否运行得足够快。如果你从来没有使用配置文件的GPU渲染,我强烈建议调查,因为那么你会得到这些漂亮的酒吧,显示你是否击中60帧一秒钟,如果你不,什么样的事情“花太多时间。
FrameLayout是我最喜欢的布局在世界上,因为它是如此令人难以置信的简单。它所能做的就是根据父边界定位事物。您可以将内容放在FrameLayout的中心内,或者将内容放置在FrameLayout的八个基本方向之一上。有很多你可以做到这一点。事实证明,如果你想在一个大屏幕的中心有一个简单的进度条,这是一个FrameLayout:你不必做任何复杂的RelativeLayout或什么有你。
它也是真正伟大的作为一个简单的布局,重叠的视图,所以如果你需要两个视图在一个顶部,FrameLayout是一个伟大的容器。它也适用于像可点击项目背景这样的东西,所以如果你有一些图像占用了很少的空间,但你想有多个视图组成一个单一的东西,你有一个FrameLayout作为父母。这实际上可以有点击检测,所以当你点击它,它看起来像发生了事情。
一个很好的例子,就像在Trello应用程序,是通知栏在右上角。这总是存在于屏幕上。它是一个单一的FrameLayout,里面有一个图标。那个白色的图标总是存在,然后如果你有未读的消息,它会把那个小红色的东西在它的顶部。白色图标居中,红色图标实际上固定在右上角,但是您可以使用边距或推动它,这样它不只是耸立在两侧。除此之外,我可以只是简单地定位这些视图,然后与可点击项目背景配对,因此当你实际点击它时,会发生一些事情。
另一件我真的很喜欢使用FrameLayouts的是我所谓的“切换容器”。如果你有两个不同的状态,你可以在之间切换,有时你有一个视图,你实际上改变。我发现它很方便有多个视图,你可以在之间切换。一个FrameLayout是一个很好的方法来包含两个完全相同的点,然后在它们之间切换。
在Trello应用程序中的一个很好的例子是头像视图。这是每当你代表卡的成员或类似的东西。如果用户有他们的头像设置,那么我们想要显示。如果他们从来没有拍过照片,那么我们要显示他们的缩写。它本质上是在图像视图或文本视图之间选择。

查看重用

头像视图引出了我想谈的下一个事情,这就是视图重用。 我们使用这个头像视图的应用程序。 这些只是三个屏幕,像Trello板。 一个打开的Trello卡,一些活动在一边,还有一些其他地方,我们在应用程序中使用头像视图。
问题变成了,如何在多个地方重用这个而不必在任何地方重写代码? 最明显的方法是使用一个叫做include的东西。

<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:orientation="horizontal"
	>
	<include
		layout="@layout/avatar_view"
		android:layout_width="48dp"
		android:layout_height="48dp"
		/>
	<!-- Rest of layout here... -->
</LinearLayout>

如果你从来没有见过它,include标签允许你指向一个直接的布局,然后就好像该布局只是复制粘贴到代码中。 您不能修改大部分包含的内容,但可以修改任何布局参数,即以layout_开头的任何内容。 这是一个很好的方式来包含可能已经匹配配对的东西,但你不想要它在最后。
这里的问题是,你在每个单一位置获取XML,但你没有得到任何逻辑。 现在你必须想出一些方法来找到include中的这些特定视图,然后添加实际绑定到视图的逻辑。 我真的喜欢这些天是使用自定义视图。

<com.trello.view.AvatarView
	android:id="@+id/avatar_view"
	android:layout_width="48dp"
	android:layout_height="48dp"
	/>

使用自定义视图,我调用而不是包含。 我有直接参考的看法。 然后我需要写实际的自定义视图本身,但它不是很难,因为这不是一个自定义视图,做自定义绘图或类似的东西。 它只是取代了那些包含的内容。
有了这个自定义头像视图,我正在扩展FrameLayout,所以我说最上面的将是一个FrameLayout。 记住,我在两个状态之间切换。

public class AvatarView extends FrameLayout {

	ImageView icon;
	TextView initials;

	public AvatarView(Context context, AttributeSet attrs) {
		super(context, attrs);

		LayoutInflater.from(context).inflate(R.layout.view_avatar, this);
		
		icon = (ImageView) findViewById(R.id.icon);
		initials = (TextView) findViewById(R.id.initials);
	}
	
	public void bind(Member member) {
		// ...Load icon into ImageView...
		// OR
		// ...Setup initials in TextView...
	}
}

我有一个ImageView和一个TextView,然后在一个构造函数本身,它实际上膨胀下面的所有视图。 作为使用头像视图的父母,我不必担心里面有什么。 它正在处理我的所有。 然后我可以有一个很好的绑定方法,其中我拿我的成员对象,并确定是否应该加载图标或加载文本。
这使我的生活很容易。 有一件值得注意的事情:如果你使用这种自定义视图设置,这是一个非常手工波形的版本将被包括的XML。

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
				android:layout_width="match_parent"
				android:layout_height="match_parent"
	>
	<TextView
		android:id="@+id/initials"
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		/>
	<ImageView
		android:id="@+id/icon"
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		/>
</FrameLayout>

但是如果你像这样包含XML,你最终会得到一个视图层次结构,顶部有头像视图。 这是一个FrameLayout,然后它膨胀另一个FrameLayout,然后具有ImageView和TextView。 这里的中间FrameLayout是没有意义的。 我们真的不需要它。
在Android中的lint检查返回一个特别严厉的消息,当你这样做,像“没有理由生活”或“没有理由存在”。我们摆脱的方式是通过一个LayoutInflater的伎俩。

LayoutInflater.from(context).inflate(R.layout.view_avatar, this);

通常当你使用LayoutInflater,无论你在哪里看到它,将有一个第三个参数,它将是false。 这是因为大多数时间都是你想要的。 但在这个特殊的情况下,你想要它是真的,这恰恰是默认的。
当它是真的,发生的是,膨胀的XML尝试附加到您作为第二个参数传递的视图组。 在这种情况下,就是这样。
然后在XML中,如果你使用称为合并标签而不是FrameLayout的东西,会发生什么,它试图将这些视图合并到父视图组,而没有任何插页式框架FrameLayout。

<merge xmlns:android="http://schemas.android.com/apk/res/android"
	>

	<TextView
		android:id="@+id/initials"
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		/>
	<ImageView
		android:id="@+id/icon"
		android:layout_width="match_parent"
		android:layout_height="match_parent"
	/>
</merge>

然后你最终得到你实际想要的层次结构:没有不必要涉及FrameLayouts。

自定义绘图

这第三个视图特定的建议与自定义绘图有关,这在特别复杂的视图的情况下是有用的。你可以通过绘制自己而不是试图找出如何将这些视图楔入到你希望它在正常视图中看起来像,以节省大量的时间。
在Trello应用程序中的一个很好的例子是标签。在这些卡上有绿色和蓝色以及红色和黄色和紫色标签。当我们第一次推出Trello应用程序时,有六种颜色,就是它;这是你可以应用到任何卡最多。而那天在工作的人不知道自定义绘图,并决定那些只是六个视图。这意味着每张卡可能有六个视图充气。
后来,Trello改变了这一点。它允许绘制任何数量的标签,所以那么你可能最终得到一个噩梦场景,其中每一张卡可以有数十个标签上,如果有人真的疯了。然后我们谈论回收这些意见。它只是变得很慢,如果你谈论这样的东西在平板电脑上,它变得非常慢,因为你可以看到更多的卡,它渲染更多的意见。这样简单得多,只需取出所有正在渲染的视图,而是有一个自定义视图绘制真正简单的形状。
有两个步骤。自定义视图以前是非常吓人我,因为我认为他们看起来真的很难,但他们真的不是。第一步是告诉自定义视图应该有多大。
需要占用多少空间?我有我的标签视图,这是非常好的,但没有人知道究竟会占用多少空间。 onMeasure是你用来告诉任何父视图组你需要多少空间。您可以跳过此步骤,因为在任何视图中,您都可以指定“我希望此视图为48 dp乘48 dp”。如果事实证明您的自定义视图总是大小相同,请完全跳过此步骤,只是在你的XML中定义它,你不必担心。
在这种特殊情况下,因为尺寸根据标签的数量而变化,我不得不自己写一些措施。

void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);

使用onMeasure,你有这个widthMeasureSpec和heightMeasureSpec,这对我来说很混乱。 事实证明,这些只是压缩的整数。 它是一个整数。 这两个参数基本上代替了四个参数,这四个参数是宽度模式和大小以及高度模式和大小。
大小只是一个维度值,但模式告诉你它是如何让你处理它通过的特定大小。 模式有三种不同的MeasureSpecs。 一个是EXACTLY,这意味着父视图组希望你是这个确切的大小。 另一个是AT_MOST,所以它占用尽可能多的空间,undefined意味着你可以定义任何理想的宽度你想要的。
你的典型onMeasure看起来像这样宽度,然后你将复制相同的代码为高度。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	int widthMode = MeasureSpec.getMode(widthMeasureSpec);
	int widthSize = MeasureSpec.getSize(widthMeasureSpec);

	int width;
	if (widthMode == MeasureSpec.EXACTLY) {
		width = widthSize;
	}
	else {
		int desiredWidth = 500; // Whatever calculation you want

		if (widthMode == MeasureSpec.AT_MOST) {
			width = Math.min(desiredWidth, widthSize);
		}
		else {
			width = desiredWidth;
		}
	}

	// ...to be continued...
}

你抓住模式和大小。 如果MeasureSpec是EXACTLY,你可能只是想传回它给你的大小。 你不想把父视图组拧得太多,否则可能会感到困惑。
否则,计算你的desiredWidth是什么,如果宽度规格是AT_MOST,那么确保你的desiredWidth不大于那个大小。 否则,如果它未定义,你只需要选择任何所需的宽度你想要的。
然后,一旦你对宽度和高度都这样做,你需要调用setMeasuredDimension为了告诉视图你决定的宽度和高度。

@Override
protected void onMeasure(int widthMeasureSpec,
		int heightMeasureSpec) {
	int width;
	int height;

	// ...Calculate width and height...

	setMeasuredDimension(width, height);
}

因为onMeasure没有返回值,所以只需在最后调用此度量。 这是衡量视图的大小,第二个是onDraw,这一个是很简单。 它只是给你一个画布,你画。
值得考虑的另一件事是,在某些情况下,你实际上不需要一个自定义视图:你可以只写你自己的自定义drawable。 并且有优势,你可以采取这个自定义编写的代码,并应用于任何视图。 这是很好,如果你想要一些特殊的自定义背景。 在这种情况下,onMeasure变成像drawIntrinsicHeight和getIntrinsicWidth这样的东西,然后ondraw变成绘图。

样式

让我们离开视图,谈论关于视图之上的另一层,即样式。 如果将XML应用于视图,则此视图没有样式。 不是因为它不酷,而是因为它上面没有样式标签。

没有风格

<View android:background=“#FF0000 />

风格

<!--- some_layout.xml -->
<View style="@style/MyStyle" />

<!--- styles.xml -->
<style name="MyStyle">
	<item name="android:background">#FF0000</item>
</style>

如果你有一个样式,这意味着我创建一些样式资源,它有相同的属性,然后视图本身然后应用该样式顶部。首先应用样式,然后将这些属性应用于其顶部。以同样的方式,包括采取布局XML和东西它到一个视图组,样式采取一堆属性和东西他们的视图。
这在哪里有用?当你需要以同样的方式为一堆语义相同的视图定义时,它非常有效。我的意思是语义上相同的是,每个视图在你的层次结构中完全相同的事情。一个很好的例子是计算器,因为在计算器中,你想要所有这些按钮,或者至少是主数字,看起来是一样的。
另一种说法是所有的样式视图应该立即改变。如果我想更改其中一个按钮的文本大小,我的期望是,他们一次更改。这节省了我一整套时间。
我看到很多人以非常低效的方式滥用风格,最终在长期内咬你。一种方式是一次性使用样式。

<TextView
	android:id="@+id/title"
	android:textColor="@color/blue_200"
	android:textColorHint=@color/grey_500" />
<TextView
	android:id="@+id/body"
	android:textColor="@color/blue_200"
	android:textColorHint=“@color/grey_500" />

这里我们有一个表示样式的视图,该样式只用了一次。这只是额外的工作,不需要在那里。有些人真的很喜欢分离所有这些代码,但它很容易重构以后创建一个风格。 Android Studio中甚至有一个重构选项可以让你这样做。
当你有两个使用相同属性的视图时,这是更重要的。说我有这两个文本视图,我说,“哦,看,他们使用相同的文字颜色和文本颜色提示,伟大的,我会在这里使用一种风格。”但如果你看看ID,你可以告诉这两个意思是彼此非常不同的东西。一个人应该betitle和一个人的身体。
假设稍后我决定,“哦,我想标题是一种不同的颜色。”嗯,如果我改变标题的颜色,这也改变了身体。所以这种风格应该是方便的,现在只是一个障碍,因为它很难修改这种风格,而没有一些意想不到的后果。
我喜欢这个例子在Java:想象我有两个常量。一个是我将在一些网格中显示的列数。另一个是在一些HTTP请求中,如果失败,我会做的重试次数。

// static final int NUM_COLUMNS = 3;

// static final int NUM_RETRIES = 3;

static final int NUM_THREE = 3;

所以我认为,“哇,这些都是相同的值,我要优化这个和有一个常数。”这是有问题的两个原因:一个是三个已经是一个常数,但另一个是我失去了所有的语义。这些数字意味着非常不同。如果我想增加HTTP的重试次数,突然,我改变了我的UI看起来也不错,顺便说一句。这些是人们可以用样式做的错误。

主题

主题类似于类固醇的风格。样式适用于单个视图。主题可以一次应用于多个视图。因此可以是一个视图组,它可以是一个活动,或者它可以是整个应用程序。它允许你应用默认样式,所以如果我想让所有的按钮看起来有点不同的应用程序,没有主题,我不得不采取这种风格,实际上添加到我所有的XML。有了主题,我可以说,“我想要一个默认样式的所有按钮,”它自动变得膨胀的一切。
主题还可帮助您配置系统创建的视图。如果你有弹出窗口或工具栏或系统创建的东西,那么你需要创建的事情就少一些。但在你可以在视图级别的主题之前,有一些问题“哦,我必须创建一些属性,只影响这个一个奇怪的弹出窗口”,但随后它拧了我的应用程序的另一部分。但是一个主题对于配置系统将创建的东西非常有用。
有三种方式来应用主题。

<application
	android:theme="@style/Theme.AppCompat">

<activity
	android:theme=@style/Theme.AppCompat.Light>

<Toolbar
	android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
	app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
	/>

您可以将其应用于整个应用程序或单个活动 - 如果这样做,它最终会覆盖应用程序中的任何内容。您还可以将其应用于单个视图。在视图的情况下,它最终实际上是重叠的,所以你可以覆盖一些个别视图的主题的变化。
查看主题非常方便。记住Holo的日子?有一个holo.light.width操作栏,这是因为没有办法主题只是屏幕的操作栏部分不同。你不得不在主题中说,“我想把大部分的屏幕定义为光,但我想这一部分是黑暗的。”而现在,你可以说,“我想要一个光主题,然后手动将暗色主题应用到工具栏本身。它使事情变得更容易。
AppCompat是Google的支持库之一,使主题更容易。它给所有设备上的材料。这是Google的最新设计语言。 Holo和Material之间在间距方面以及在他们使用的视觉隐喻方面有很多细微的差别。从一个单一的基线开始,然后从那里开始主题就容易多了。
AppCompat给你的另一个东西是基线主题和风格。你可能想改变所有按钮的默认外观,但是你不想实际去定义一个风格,它定义了按钮必须具有的每一个属性。你只是想采取主要和调整它,像添加一点padding到所有的按钮。 AppCompat使得轻松获取AppCompat按钮样式并从中扩展和修改它。没有它,它变成了一种噩梦,特别是在Holo和材料之间。
AppCompat支持的第三个非常重要的事情是,它允许你在XML中查看pre-Lollipop的主题。这是我最喜欢的事情之一,因为Lollipop有这样的看法,这看起来很酷,但我是,“哦,但你不能得到它backported。”他们实际上设法退回一路返回到一些API你不应该甚至不再使用,我认为11.(对不起,人们仍然需要支持11上的应用程序)
这里有几个例子,你可以做的主题。 ColorTheming无处不在。

<style name="ColorTheme" parent="Theme.AppCompat">
	<item name="colorPrimary">#F00</item>
	<item name="colorPrimaryDark">#0F0</item>
	<item name="colorControlNormal">#00F</item>
</style>

而不是必须使用单独的drawables的一切,我可以只设置颜色,Android中的大部分的东西将自动着色。
这些是应用默认样式的一些示例。

<style name="AppTheme" parent="Theme.AppCompat">
	<item name="buttonStyle">@style/MyButton</item>
	<item name="android:spinnerItemStyle">@style/MySpinnerItem</item>
	<item name="android:textAppearance">@style/MyText</item>
	<item name="android:textAppearanceInverse">@style/MyTextInverse</item>
</style>

为了防止以前从未见过,顶行定义了整个应用程序的按钮样式。 这将应用于每个按钮。 微调项目样式是方便的,因为如果我想使用Android提供的内置微调项目布局行,但我想要风格一点点? 我可以在这里使用。 文本外观很好,因为它可以应用于文本视图,然后你仍然可以应用另一种风格。
另一个有用的事情你可以做的主题是设置属性,然后在您的XML中引用。

<style name="AttrTheme" parent="Theme.AppCompat">
	<item name="selectableItemBackground">@drawable/bg</item>
</style>

<!-- some_layout.xml -->
<Button android:background="?attr/selectableItemBackground" />

所以在这种情况下,selectableItemBackground(我最喜欢的属性之一)引用了?属性/而不是通常使用的@drawable,它从主题派生该值,而不是直接去。
为什么这很有用?如果你碰巧有一个应用程序支持多个主题,它使得很容易交换这些值之间。更重要的是,你的系统可能有一个selectableItemBackground的多个想法,因为pre-Lollipop没有任何Ripple可绘制。它只是一个平坦的颜色,你改变到每当你点击的东西。后棒棒糖,你想有这些涟漪,因为它看起来很酷。如果你使用aselectableItemBackground,主题可以自动找出它想要采取的。

资源

资源是所有进入你的应用程序的东西,不只是纯Java代码。在谈谈资源之前,我想谈谈设备配置。
如果我们看看这个屏幕截图,有一大堆事情,人们可以得出它的配置。例如,我可以说它是纵向的,它的高度为731密度在一品脱的像素,它的宽度为411,它是一个Nexus 6p,所以它的密度为xxxhdpi,它碰巧是现在英语,美国语言环境,所以它显示en_US,它的版本24.这些都是Android系统知道的设备,你可以手动查询这个人自己,如果你想要的。有了资源,你可以让它自动选择的东西。
这些设备的一些东西将在整个执行过程中改变,有些不会。所以纵向和横向,除非你锁定你的方向,这可以改变非常快。用户可能不会经常更改区域设置,但可以在应用程序运行时更改它。然后一些东西,比如密度和什么操作系统版本可能不会改变,而你运行你的应用程序。
那么,你想改变什么样的事情呢?景观与肖像是一个典型的例子,因为它通常呈现不同的操作模式。内置的计算器应用程序,当它在肖像,只显示四行,但当它有更多的空间伸展,它可以显示一些冷却器的功能默认情况下。
Locale是一个非常简单的:如果你想让你的应用程序翻译成不同的语言,你只需要根据语言环境选择不同的文本字符串。在左边这是英语,右边是日语。你可以在屏幕的宽度上打破事情,所以在手机上的卡,当它打开时足够小,它只是决定占用全宽,而在某些时候,如果设备变得足够大,它只是看这是一个可笑的,它是全宽度,所以我们开始有一个断点在某一时刻与宽度。
另一个例子是我们的搜索结果。我们有交错的网格视图,再次,在宽平板电脑上,没有一个列有意义。尽可能地填充它是有意义的,因此我们可以基于此改变列的数量。然后在手机上你可以看到顶部的结果是一些小板显示,因为它是一个小设备,而在较大的平板电脑,我们可以显示漂亮的大矩形。
你可以在Java代码中做所有这些,就像我前面所说的,但是如果你只是利用资源限定符系统,它会更容易。您可以为不同的设备配置定义备用资源,然后在运行时,Android将根据设备配置自动选择正确的资源。它遍历和查询一切,找出您定义的哪些资源在这种情况下最有意义。
您可以通过文件夹的名称来定义它。在你的resources目录中,如果你有一些只是默认值,这意味着它没有资源限定符附加到它,它是所有情况下的回退。而如果你做一个短划线然后一个资源限定符,所以这一个有一个资源限定符,它的xxxhdpi,如果你想要的话,你可以有多个限定符。您可以对单个值应用尽可能多的限定符,虽然通常它不是很方便,如果你做到许多不同的值。
另一件值得注意的事情是,如果你有多个限定符,他们必须按特定的顺序,所以查找文档;有一个巨大的表所有不同的限定符,你可以使用,你必须把它们的表的顺序为Android来正确解析它。

同样的文档页面也列出了算法,但它只是一个消除的过程。它试图找到给定当前配置的最具体的资源。所以想象我从一些价值开始,我有一些最小宽度600dp的值。最小宽度意味着无论方向如何,您可能拥有的设备的最小宽度,这对于了解设备类(如平板电脑和手机)很有用。它也必须在肖像。

然后它会从这里选择,如果这些是真的,但如果事实证明一个或另一个不是真的,那么它会开始寻找看到其他可以消除的事情。然后它会看起来可能只是单一的sw600dp,哦,结果是手机不符合资格,所以然后它会检查手机是否是肖像,如果它不符合资格,那么,它会回到这里的基本价值。

这就是为什么它是方便的有一个默认值的一切。你唯一不需要默认值的是drawables,因为Android的工作方式,它会自动缩放,如果你没有在正确的目录中的东西。所以如果你只有xxxhdpi资产,你的设备恰好是mpi,它只是将一切都放大,这是不是很好的性能明智,因为所有额外的工作,但至少你不必担心当你快速发展。
在以正确的方式使用资源限定符方面,将这些资源看作代码很重要。想想你插入某个地方的每个资源作为某些方法或函数的参数,并且该参数是根据设备配置确定的。例如,左边的代码是疯狂和愚蠢的,因为我必须为每个数字写一个新的方形函数,我想要平方,而右边的代码有这个参数。你想更多地考虑它的代码在右边。
我喜欢使用很多的一个简单的例子是让资源限定符系统为我确定一些布尔逻辑。这是一个简单的,我只想知道它是否在肖像或不。是的,你可以很容易地从资源查询这个,但这只是一个例子。所以我可以说,“默认情况下,is_portrait是假的,当它是在纵向,它是真的”,然后我可以得到这个布尔值。如果你有多个不同的配置和布尔逻辑可以运行的多种方式,这是真的很方便。它可以为你做所有的计算。
一个更经典的例子是使用它为不同的布局。所以我可以说我要调用setContentView,我有这三个不同版本的布局,一个是默认,一个以横向显示,一个以纵向显示。 (我做这个幻灯片之前,我意识到这是不可能的,实际上结束了,没有它在风景或肖像,你必须有一个方形屏幕。)
它会选择正确的一个,但你可能最终会得到一些重复的代码,因为在肖像和风景之间的机会没有那么多的变化。然后,如果你重用代码与一个包含,它可以打开只是改变的那部分代码。在这个例子中,我有我的LinearLayout,它里面有一个包含,这是唯一的部分,基于方向更改。现在我可以有一个单一的活动主,我可以有一个布局是默认的,然后布局,只是在纵向修改。
沿着同样的路线,让我们看看包括。假设这两个包含文本视图,它们应该是几乎相同的东西,但修改取决于文本的大小。在这里,我可以做的是引用一个维度,然后该维度也可以基于限定符来确定。

<TextView
	android:layout_width="wrap_content"
	android:layout_height="match_parent"
	android:textSize="@dimen/welcome_text_size"
	android:textColor="#FF00FF"
	/>

为了进一步,让我们假设我们在应用程序中的某处有样式。所有修改的是文本大小,所以我可以再次有一个维度,但现在这种风格可以应用于所有的应用程序,而不是应用该维度技巧只是一个特定的视图。在这个例子中,我有一个活动主要在顶部,然后默认去一个包括,但如果它恰巧在纵向,它会去一个不同的包括。两者都包含相同的文本视图,因此它们使用相同的样式。然后,根据当前配置,该样式决定文本大小。
你可以深入这一点,并写所有的布局之间很少重复的代码,如果你正在做的是改变基于设备配置的事情。这就是为什么一般来说,你不应该覆盖在Android上的配置更改。这是一个很常见的初学者的方式来解决的问题,“哦,我旋转我的手机,然后我的活动被毁了,我不想发生,因为我的所有数据在哪里?”然后有人说,“嘿,如果你只是重写配置更改,一切正常,所有的数据保持在周围。
有两个问题。一个是,它不一定帮助你,因为你可能只是覆盖配置更改方向更改,但有很多其他的方式,配置可以改变。二,这意味着你完全绕过这个系统,因为你基本上告诉Android系统,“我有这个,不要担心。”这整个资源限定词系统是为什么当你旋转你的手机,活动被销毁并再次重新创建;因为它想让你重新膨胀一切,当你重新膨胀一切,一些可能已经改变基于选择不同的布局。

Drawables

我想概述一个你可能已经经历的噩梦场景。 (我当然经历了这么多次。)我正在与设计交互,他们给我一个新的登录屏幕的模拟,他们想添加这个“登录SSL”的东西在底部。
我开始工作,但我告诉设计,“我需要这个资产,因为我不善于设计或任何东西,我需要你给我这个。”所以设计说,“好吧,肯定没有问题。 “他们发送一个zip文件,我解压缩,我得到这个文件是谁 - 谁知道这应该是多大。所以我讲设计。 “好吧,这还不够,我需要更多的,我需要一个在所有不同的密度。”和设计说,“哦,肯定,”他们去做一些研究如何工作,然后他们发回这样的文件,和一个包含这个文件的zip文件。
现在我拥有了所有需要的资产,但是我必须通过并重命名一切,并将其放在正确的文件夹中,然后导入我的项目。这是每一个资产的真正痛苦。然后在所有这些之上的踢球者是,在最后,设计说,“实际上,我想调整颜色,这里是一组新的资产。”所以现在我必须经历这个整个过程,这是一个巨大的痛苦的屁股。
我一直在Trello的设计团队工作,我们想出了一大堆方法来减少所有这些痛苦和所有这些摩擦。将资产视为尽可能多的代码。不要把它们当作你从设计中得到的位图。将它们视为可以在应用程序中执行的事情。因为这样就可以快速地调整和改变事物。
第一个例子是可绘制的XML,从一开始就一直在Android中。和可绘制的XML是你可以定义的资源:你可以绘制简单的形状和设置状态选择器。 (如果你按下一个按钮,它看起来有点不同,这是一个状态选择器。)你可以使用它作为一个图层列表,这是真的很方便,因为如果你有两个可绘制,你实际想要层叠在彼此之上你可能会想,“好吧,现在我需要设计来为我复合所有这些图像。”实际上,你可以设置一个图层列表,然后单独更改这两个图层,你得到那个很好的组成。
一个详细的例子是我工作过的登录按钮一次。这些登录按钮完全通过可绘制的XML完成。使这些登录按钮工作的第一步是创建该按钮大纲。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
		android:shape="rectangle"
	>

	<solid android:color="@color/transparent" />

	<stroke
		android:width="1dp"
		android:color="@color/white"
		/>

	<corners android:radius="@dimen/corner_radius_tiny" />
</shape>

按钮轮廓本身只是它自己的文件。 所以我们现在不用担心点击状态。 它使用可绘制的形状。 第一个重要的部分是你可以告诉它你想要什么类型的形状。 你也可以做椭圆和这样的东西。 但是你只能使用可绘制XML的非常简单的形状。
我想说,它大多是充满透明的空间; 事实上,这应该是默认,但在一些较旧的版本的Android它没有默认为透明的实体。 然后我想要那个白色轮廓,所以我给它一个笔划,然后确定轮廓,我想有一个小半径,所以它得到了漂亮的小漂亮的按钮看起来。 所以这只是创建大纲,蓝色实际上来自整个屏幕的背景。 我只是把它放在那里,否则将很难看到我的幻灯片上的白色背景。
然后我们需要添加一些行为。

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

	<item>
		<selector>
			<item android:state_pressed="true">
				<shape android:shape="rectangle">
					<solid android:color="@color/blue_200" />

					<corners android:radius="@dimen/corner_radius_tiny" />
				</shape>
			</item>
	
			<item android:drawable="@color/transparent" />
		</selector>
	</item>

	<item android:drawable="@drawable/btn_welcome_outline" />
</layer-list>

当我点击它,我想实际上能够告诉我点击它,所以我们需要添加一个选择器,它可以看到在这个漂亮的小两帧GIF。我这样做是通过在我刚刚谈到的大纲的顶部分层一个选择器。
我使用一个图层列表,该图层列表允许我采取两个可绘制,并把一个在另一个之上。我说的顶层是我刚刚向你展示的大纲。这总是要画。然后另一层是一个选择器,选择器只有两个状态。当它被按下,我想画这个其他的形状。
再次,我使用另一种形状drawable为了确定什么应该在它内部绘制,在这种情况下,它是一个更简单,因为我可以说,“我想让你有一个纯色,但也有角落,所以它不会结束流失的角落。“然后,当它没有按下,在默认状态,它将只是透明的。
这是伟大的和所有,但是在Android的21版本中,他们添加了这些漂亮的波纹可绘图看起来真的很漂亮,这需要一组不同的代码。

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
		android:color="@color/blue_200"
	>

	<item android:drawable="@drawable/btn_welcome_outline" />

	<item android:id="@android:id/mask">
		<shape android:shape="rectangle">
			<solid android:color="@color/white" />
			<corners android:radius="@dimen/corner_radius_tiny" />
		</shape>
	</item>

</ripple>

为此,你使用波纹XML,它是在版本21中添加的。里面有指定波纹的颜色。在那之后,我仍然有相同的按钮轮廓,实际上是在那个涟漪画,但然后最后一部分定义了一个面具。这个面具基本上说:“这是纹波应该出现的地方的轮廓。”然后,它里面的实际颜色并不重要。这只是事实,这个drawable将吸引到这个重要的特定领域。
所以我能够逃避所有这一切,并有不同版本的不同版本的Android,通过使用资源限定词系统。在底部有这个大纲,我总是要使用,但然后默认按钮使用状态选择器,这将始终工作一直回到Android的版本。可绘制的XML是一个很好的方式来跳过很多工作。然后我只是要问设计的颜色。我不需要任何东西。
矢量drawables。形状drawables让你绘制非常简单的形状。没有什么复杂。矢量drawables让你做任何类型的矢量绘图你想要的。它允许你做非常复杂的形状,使用向量的优势是你不必担心屏幕密度这么多。因为在我不得不从所有不同密度的设计中获得这些PNG。在这里,他们可以给你一个单一的矢量,然后它会自动绘制任何最佳的分辨率为该屏幕。这是一个巨大的节省时间,但是有一个问题与Android实现的矢量绘图的方式。
最近在Android中添加了矢量drawables,但在支持库中有一个向后兼容的库,使用向量一直返回到14或类似的东西。而Android有一个很大的问题,那就是他们想出了自己的向量可绘制格式,而不是实际的SVG。如果你的设计师是我的设计师,他们知道如何说SVG真的很好,他们的所有工具都知道如何在SVG中输出,没有人知道如何输出为矢量可绘制。你需要一些方法来转换这些SVG,你的设计师给你在应用程序中的矢量可绘制。
有两种方法。一个是,在Android Studio中,你可以说,“我想要一个新的向量资产”,这将带来这个漂亮的小小的向导,然后你可以传入SVG,它会把它转换为向量可绘制为最好它可以。有一些SVG,它不能很好地工作,不会转换。这很好,但我不想每次导入新资产时都必须经过向导。所以,相反,我们在所有矢量可绘制的兼容的东西发生之前,我们写了,但我们仍然使用它 - 有这个Android插件,我们写作Victor。
Victor允许您定义任何数量的源集,在任何地方,你有你的SVG,它会清除所有这些,然后输出系统可以呈现的东西。有一段时间,它只是输出PNG,但然后我们能够实际抓住这个新的矢量资产的东西,并使用它来转换成直线矢量可绘制的代码。这是伟大的,因为与Trello,我们的设计师有自己的Git存储库,这是他们放置所有的编译的SVG。我们可以把它作为一个Git子模块并导入,然后我们只需更新一个提交指针,从设计中获取新的资产。
我最后想说的drawables,最近真的节省了很多时间,是skeuomorphic和平面设计的区别。 Skeumorphic设计是你有东西,看起来完全像他们应该是什么。左边是Andie Graph,这是我的朋友写的一个应用程序,使您的手机的行为完全像德州仪器的科学计算器,看起来就像它。它看起来很现实。这是skeumorphic。
在右边,你有普通的计算器应用程序,它是平的,每个按钮只是这个平坦的颜色。好像左边的东西看起来,右边所有的图标和所有的文字只是平坦的颜色。它很容易染色这些颜色,并在飞行中改变。用左边的按钮,这将是非常困难的,以任何方式,它是合理的。
在Trello应用程序中,我们的所有资产都是平黑色。他们是黑色的alpha,然后在代码中,我们采取这些和着色任何我们想要的颜色。从设计的角度来看,这是超级便利,因为他们不必为任何想要的颜色创建多个资产。每次他们想改变颜色,我们可以改变它在代码。
在着色图像方面,有几种方法来做。
一个是通过XML,但是除了图像视图tint属性不能很好地工作,它不是向后兼容的事实。 它被添加到最新版本的Android中,以便能够在XML中绘制可绘制项,但他们还没有想出任何方法来实际上退出该功能。

我最终做了大部分的着色代码。

简单

drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);

全面

Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
DrawableCompat.setTint(wrappedDrawable, color);

这是非常简单的图像视图和可绘制。你可以调用setColorFilter并只是传递颜色。然后,对于Alpha上的黑色图标,您要使用SRC_IN的PorterDuff.Mode。
现在如果你想要一个真正全面的解决方案,库支持DrawableCompat。这样你实际上包装drawable然后调用setTint或setTintList上的wrappedDrawable。直接调用颜色过滤器的主要优点是它可以处理色调列表,所以你可以有多个不同的选择状态的包装的drawable的颜色,你可以调色所有它。但是,由于我们并没有在应用程序中使用它,我们不会经常这样做,所以设置颜色过滤器只是一个更快,更容易的方法。

结论

在我的博客danlew.net我已经写了更多关于这些事情,这次演讲的一些部分是从更详细的其他会谈,特别是风格和主题。你可以看看我的老音箱甲板找到这些演讲,如果你更有兴趣学习一些更细节的细节。非常感谢你。


©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页