forked from hehonghui/android-tech-frontier
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' of github.com:bboyfeiyu/android-tech-frontier
- Loading branch information
Showing
13 changed files
with
2,048 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
多文本布局 | ||
--- | ||
> * 原文链接 : [Multiple Text Layout](https://sriramramani.wordpress.com/2014/08/14/multiple-text-layout/) | ||
* 原文作者 : [Sriram Ramani](https://sriramramani.wordpress.com/author/sriramramani/) | ||
* [译文出自 : 开发技术前线 www.devtf.cn](http://www.devtf.cn) | ||
* 译者 : [dengshiwei](https://github.com/dengshiwei) | ||
* 校对者 :[chaossss](https://github.com/chaossss) | ||
* 状态 : 已完成 | ||
|
||
在Android中开发UI的最基本的单元就是一个视图(View)。但是,如果我们仔细观察,视图(View)是一个与用户交互的UI控件。它包含Drawables和text [Layouts](http://developer.android.com/reference/android/text/Layout.html)。我们随处可见drawables —— 视图(View)的背景(backgroud)。TextView同样由drawables组成。然而,TextView只有一个布局(layout)。在一个View/TextView中是否可能有多于一个的(布局)layout? | ||
![FIRST](https://sriramramani.files.wordpress.com/2014/08/badges.png) | ||
|
||
让我们看一个例子。我们有一个简单的ListView,它每行中包含一个image、text以及一些sub-text。由于TextView中显示默认只有一个文本布局(text Layout),我们需要一个包含2个或3个视图(views)的LinearLayout来实现这种布局(layout)。如果TextView能够容纳一个以上的布局(layout)将会是什么(情况)?即使它(TextView)能够容纳和绘制,它也是一个可以创建和绘制的私有变量(private variable)。我们如何才能够让TextView的原始布局(original layout)实现这种布局(layout)呢? | ||
|
||
如果我们仔细看TextView的onMeasure方法,布局(layout)的可用width占据着compound drawables所占用的空间。如果我们让TextView占有compound drawable右侧的大部分,这个布局(layout)将会包含自己更多。现在空间划分出来了,我们可以在这个空间绘制(draw)布局。 | ||
|
||
private Layout mSubTextLayout; | ||
|
||
@Override | ||
public int getCompoundPaddingRight() { | ||
// Assumption: the layout has only one line. | ||
return super.getCompoundPaddingRight() + mSubTextLayout.getLineWidth(0); | ||
} | ||
|
||
现在我们需要为sub-text制造一个布局(layout)并绘制它。理想情况下,在onMeasure()方法中去创建一个对象是不好的。但是如果我们注意何时以及如何创建布局(layouts),我们不需要担心这个限制。 | ||
我们可以创建什么种类的布局(layouts)呢?TextView允许创建BoringLayout,BoringLayout是一个 StaticLayout或DynamicLayout。BoringLayout用于文本(text)如果只有一行的情况下。StaticLayout 用于multi-line的布局(layouts),它创建后不能改变。DynamicLayout 用于可编辑的文本(editable text),如同EditText。 | ||
|
||
@Override | ||
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | ||
int width = MeasureSpec.getSize(widthMeasureSpec); | ||
|
||
// Create a layout for sub-text. | ||
mSubTextLayout = new StaticLayout( | ||
mSubText, | ||
mPaint, | ||
width, | ||
Alignment.ALIGN_NORMAL, | ||
1.0f, | ||
0.0f, | ||
true); | ||
|
||
// TextView doesn't know about mSubTextLayout. | ||
// It calculates the space using compound drawables' sizes. | ||
super.onMeasure(widthMeasureSpec, heightMeasureSpec); | ||
} | ||
|
||
此处的mPaint包含对于sub-text的所有属性,例如字体颜色(text-color)、阴影(shadow)、字体大小(text-size)等等。这些决定用于text layout的大小。 | ||
|
||
@Override | ||
public void onDraw(Canvas canvas) { | ||
// Do the default draw. | ||
super.onDraw(canvas); | ||
|
||
// Calculate the place to show the sub-text | ||
// using the padding, available width, height and | ||
// the sub-text width and height. | ||
// Note: The 'right' padding to use here is 'super.getCompoundPaddingRight()' | ||
// as we have faked the actual value. | ||
|
||
// Draw the sub-text. | ||
mLayout.draw(canvas); | ||
} | ||
|
||
但是,我们不能只使用Spannable文本(text),好吧!如果这个名字确实很长,并且已经形成多行或者需要ellipsized。 | ||
|
||
通过这样,我们使用同一个TextView可以绘制两个布局(layouts),同时也帮助我们移除了2个视图(Views)! Happy hacking! | ||
|
||
P.S: 这些图标来自: [http://www.tutorial9.net/downloads/108-mono-icons-huge-set-of-minimal-icons/](http://www.tutorial9.net/downloads/108-mono-icons-huge-set-of-minimal-icons/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
#Android LayerDrawable 和 Drawable.Callback | ||
|
||
> * 原文链接 : [Android LayerDrawable and Drawable.Callback](http://www.roman10.net/android-layerdrawable-and-drawable-callback/) | ||
* 原文作者 : [Liu Feipeng](www.roman10.net) | ||
* 译文出自 : [开发技术前线 www.devtf.cn。未经允许,不得转载!](www.devtf.cn) | ||
* 译者 : [Desmond1121](https://github.com/desmond1121) | ||
* 校对者:[bboyfeiyu](https://github.com/bboyfeiyu) | ||
|
||
`LayerDrawable`是一个特殊的`Drawable`,它内部保持着一个`Drawable`数组,其中每一个`Drawable`都是视图中的一层。如果你不了解`LayerDrawable`的机制,当程序出了问题后是很难去找到bug在哪里的。我发这些文章就是为了分享在使用`LayerDrawable`与`Drawable.Callback`时可能出现的一个bug。 | ||
|
||
##Callback调用链 | ||
|
||
在`LayerDrawable`中,每层视图(`Drawable`)都会将`LayerDrawable`注册为它的`Drawable.Callback`。这允许`Drawable`能够在需要重绘自己的时候告知`LayerDrawable`重绘它。我们可以在下面这个`Callback.invalidateSelf()`函数中看到是由注册callback端(在此处为`LayerDrawable`)来执行`invalidateDrawable(Drawable drawable)`的。 | ||
|
||
```java | ||
public void invalidateSelf() { | ||
/* 获取注册的Callback实例,如果无则返回null。 */ | ||
final Callback callback = getCallback(); | ||
if (callback != null) { | ||
callback.invalidateDrawable(this); | ||
} | ||
} | ||
``` | ||
|
||
我们知道`View`是实现了`Drawable.Callback`接口的,所以当图片需要重绘的时候就能够告知`View`。如果我们把`View`的背景图片设置成了`LayerDrawable`,在`Drawable`需要更新的时候callback的调用将有一个传递的过程,首先会调用注册的`LayerDrawable`的`invalidateDrawable(Drawable drawable)`方法,`LayerDrawable`又会调用`View`的`invalidateDrawable(Drawable drawable)`方法。如下图所示: | ||
|
||
![Alt text](http://img.blog.csdn.net/20150818142917049) | ||
|
||
##View改变背景时移除原背景Callback | ||
|
||
在`View`的`setBackgroundDrawable(Drawable background)`中有这么一段代码: | ||
|
||
```java | ||
if (mBackground != null) { | ||
mBackground.setCallback(null); | ||
unscheduleDrawable(mBackground); | ||
} | ||
|
||
… | ||
if (background != null) { | ||
background.setCallback(this); | ||
} | ||
``` | ||
|
||
我们可以看出:当`View`改变背景时将会**无条件将原背景(如果原背景是Drawable的话)的`Drawable.Callback`设置为`null`。** | ||
|
||
##什么情况下会出现Bug? | ||
|
||
有了上面这些知识,我们可以通过下面这个步骤产生一个Bug: | ||
|
||
> 1. 把`Drawable`**A **设置成`View`**V**的背景。**现在A的callback指向V**。 | ||
2. 将A设置成`LayerDrawable` **L**中的一层。**现在A的callback指向L**。 | ||
3. 现在为**V**设置另一个背景,**V会把原背景(A)的callback强制设置成null,破坏了A与L之间的联系。** | ||
4. **BUG出现了**:更新`Drawable`**A**不会让**L**更新了。 | ||
|
||
|
||
解决方法就是在更新**V**的背景之后再创造`LayerDrawable`**L**。Bug发生与解决的例子可以在[这里](https://github.com/roman10/blog-android-source-code/tree/master/LayerDrawableCallback)下载。 | ||
|
||
为了方便看官,我也贴了一部分关键代码到这边来,你可以通过注释理解这段代码。 | ||
|
||
@Override | ||
protected void onCreate(Bundle savedInstanceState) { | ||
super.onCreate(savedInstanceState); | ||
setContentView(R.layout.activity_main); | ||
Button btn1 = (Button) findViewById(R.id.button1); | ||
btn1.setOnClickListener(new View.OnClickListener() { | ||
@Override | ||
public void onClick(View v) { | ||
// 1. 将 launcherIconDrawable.callback 赋值给 actionBar | ||
actionBar.setBackgroundDrawable(launcherIconDrawable); | ||
animateActionBarWorking(); | ||
} | ||
}); | ||
Button btn2 = (Button) findViewById(R.id.button2); | ||
btn2.setOnClickListener(new View.OnClickListener() { | ||
@Override | ||
public void onClick(View v) { | ||
// 1. 将 launcherIconDrawable.callback 赋值给 actionBar | ||
actionBar.setBackgroundDrawable(launcherIconDrawable); | ||
animateActionBarNotWorking(); | ||
} | ||
}); | ||
actionBar = getSupportActionBar(); | ||
launcherIconDrawable = getResources().getDrawable(R.drawable.launcher_repeat); | ||
colorLayer = new ColorDrawable(Color.rgb(0, 255, 0)); | ||
actionBar.setBackgroundDrawable(colorLayer); | ||
} | ||
|
||
/* 这个函数运行后ActionBar不会得到更新。 */ | ||
private void animateActionBarNotWorking() { | ||
Drawable[] layers = new Drawable[] { colorLayer, launcherIconDrawable }; | ||
LayerDrawable layerDrawable = new LayerDrawable(layers); | ||
actionBar.setBackgroundDrawable(layerDrawable); | ||
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 255); | ||
valueAnimator.setDuration(1000); | ||
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { | ||
@Override | ||
public void onAnimationUpdate(ValueAnimator animation) { | ||
// 4. Updates launcherIconDrawable will not trigger action bar background to update | ||
// as launcherIconDrawable.callback is null | ||
launcherIconDrawable.setAlpha((Integer) animation.getAnimatedValue()); | ||
} | ||
}); | ||
valueAnimator.start(); | ||
} | ||
|
||
/* 由于先移除了launcherIconDrawable与ActionBar的联系,这个函数运行后会让ActionBar得到更新。 | ||
private void animateActionBarWorking() { | ||
actionBar.setBackgroundDrawable(null); | ||
animateActionBarNotWorking(); | ||
} | ||
|
||
参考文章: | ||
|
||
[LayerDrawable - Android Developers](http://developer.android.com/reference/android/graphics/drawable/LayerDrawable.html) |
Oops, something went wrong.