建立数据驱动的分层视图(翻译)

应用程序中有一种很常见的情况是由事件数据驱动UI的显示。本教程中将展示一个我几乎每天工作中都用到的非常有用的技术,这是此前一个非常有才华的工程师教给我的。用于建立多个自定义的视图并用于不同的布局,然后将数据更新到每个布局的一个容器视图里。本教程的所有代码都可以在GitHub上面找到。

英文好且有梯子的请戳这里看原文。

在这个例子中我们要做的第一件事就是创建一个用于更新视图的接口。这个接口只有一个update方法,用于更新视图数据。

1
2
3
public interface Updateable {
public void update( Weather weather );
}

接下来,我们将实现一个简单的数据模型,容纳我们用于驱动视图的所有数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Weather {
private int temperature;
private int windSpeed;
private WindDirection windDirection;
private WeatherCondition condition;

public Weather( int temperature, int windSpeed, WindDirection windDirection, WeatherCondition condition ) {
this.temperature = temperature;
this.windSpeed = windSpeed;
this.windDirection = windDirection;
this.condition = condition;

}

public void setTemperature( int temperature ) {
this.temperature = temperature;
}

public int getTemperature() {
return temperature;
}
...

WindDirectionWeatherConditon只是一些像这样的枚举类型:

1
2
3
4
5
6
7
8
public enum WeatherCondition {
RAIN,
SNOW,
LIGHTNING,
SUN,
CLOUDY,
FOG
}

现在我们已经创建好了数据模型和接口。接下来开始为我们的项目创建updateable视图。为了开始,我们不用标准的只视图,在这里我们修改TextViewImageView,然后将要触发更新状态的的子视图在其容器视图里面更新。我们第一个updateable视图是WeatherImage,只是简单的继承了ImageView并实现了Updateable接口。

1
public class WeatherImage extends ImageView implements Updateable

update方法中,我们检查Weather对象所准备的weatherCondition的数据,并根据数据展示一张图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Override
public void update( Weather weather ) {
Log.e( "WeatherImage", "update!" );
if( weather == null || weather.getWeatherCondition() == null )
return;

switch( weather.getWeatherCondition() ) {
case CLOUDY: {
setImageResource( R.drawable.cloudy );
break;
}
case FOG: {
setImageResource( R.drawable.fog );
break;
}
case LIGHTNING: {
setImageResource( R.drawable.lightning );
break;
}
case RAIN: {
setImageResource( R.drawable.rain );
break;
}
case SNOW: {
setImageResource( R.drawable.snow );
break;
}
case SUN: {
setImageResource( R.drawable.sun );
break;
}
}
}

我们用同样的方法实现了WindWeatherTextViewWeatherTextViewWeatherTemperatureTextView。只是把继承的ImageView改为TextView,如名字所示那样。

真正神奇的地方发生在这个为其他可更新子视图创建的容器UpdateableLinearLayout这里。这个容器同样实现了Updateable接口,然而并不是用于调整自身,而是调用了其他子视图的update方法。

1
public class UpdateableLinearLayout extends LinearLayout implements Updateable
1
2
3
4
5
6
7
8
9
@Override
public void update( Weather weather ) {
Log.e("UpdateableLinearLayout", "Update!" );
if( weather != null && mUpdateableViews != null && !mUpdateableViews.isEmpty() ) {
for( Updateable view : mUpdateableViews ) {
view.update( weather );
}
}
}

mUpdateableViewsUpdateable对象们的一个列表,为了创建一个完整的列表视图,它通过循环和递归找到UpdateableLinearLayout和其子视图中所有实现了Updateable接口的对象,并填充onFinishInflate()方法中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mUpdateableViews = findTopLevelUpdateables( this );
}

public List<Updateable> findTopLevelUpdateables( ViewGroup view ) {
ArrayList<Updateable> results = new ArrayList<Updateable>();

int childCount = view.getChildCount();
for( int i = 0; i < childCount; i++ ) {
results = findTopLevelUpdateables( view.getChildAt( i ), results );
}
return results;
}

protected ArrayList<Updateable> findTopLevelUpdateables( View view,
ArrayList<Updateable> results ) {

if( ( view instanceof ViewGroup ) && !( view instanceof Updateable ) ) {
ViewGroup viewGroup = (ViewGroup) view;
int childCount = viewGroup.getChildCount();
for( int i = 0; i < childCount; i++ ) {
findTopLevelUpdateables( viewGroup.getChildAt( i ), results );
}
}

Updateable result = ( view != null && view instanceof Updateable ) ? (Updateable) view : null;
if( result != null ) {
results.add( result );
}
results.trimToSize();
return results;
}

同样的方法可以很容易的用到RelativeLayout或者任何其他的ViewGroup中。

一旦我们的类都创建好了,我们可以把他们放到用户的布局文件中,如下面的activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<com.ptrprograms.eventdrivenhierarchicalviews.view.UpdateableLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<com.ptrprograms.eventdrivenhierarchicalviews.view.WeatherImage
android:layout_width="match_parent"
android:layout_height="300dp" />

<com.ptrprograms.eventdrivenhierarchicalviews.view.WeatherTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24sp" />

<com.ptrprograms.eventdrivenhierarchicalviews.view.WeatherTemperatureTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24sp" />

<com.ptrprograms.eventdrivenhierarchicalviews.view.WindWeatherTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24sp" />

</com.ptrprograms.eventdrivenhierarchicalviews.view.UpdateableLinearLayout>

如你所视,在这个布局中,UpdateableLinearLayout是我们的根ViewGroup,它所有的子View都是实现了Updateable接口的。然而你也可以使用那些没有实现Updateable的View,只是当数据改变的时候他们不会自动更新。

在这个例子中,我们使用UpdateableLinearLayout声明该布局,并在MainActivityonCreate方法中初始化:

1
2
setContentView(R.layout.activity_main);
mRootView = (UpdateableLinearLayout) findViewById( R.id.root );

在这里,我们没有使用一个活动的API来驱动我们的数据,只是简单的创建了一个方法来模拟每三秒产生一个随机的Weather数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void startSimulation() {
mHandler = new Handler();
Runnable runnable = new Runnable() {
@Override
public void run() {
if( mCurrentItem >= mWeather.size() )
mCurrentItem = 0;

mRootView.update( mWeather.get( mCurrentItem++ ) );

if( mHandler != null )
mHandler.postDelayed( this, 3000 );
}
};

runnable.run();
}

这样,我们就有个由数据驱动的自定义视图。并可以在不同的地方建立多个布局文件重复使用这些自定义视图。这个技术在我日常开发中价值不可估量,我希望它对其他的安卓开发者同样有用。

听说,赞赏了的人都变美了哦~
0%