【问题标题】:Coupling Android layout to its logic outside of the owner Activity将 Android 布局与所有者 Activity 之外的逻辑耦合
【发布时间】:2012-03-19 22:09:36
【问题描述】:

在开发中等复杂的 Android 应用程序时,我遇到了一些复杂问题。我正在搜索有关使用类似code-behind 的技术以更轻松地维护 Android 软件的可能性的信息。

目前(请突出显示任何错误),我发现为了制作带有额外对话框的多步骤向导(例如,不属于主序列的对话框),我需要编写一个 XML 布局文件包含每个子视图作为子节点的单个 ViewFlipper。今天,我发现了如何在视图之间导航而不是向前/向后 (viewFlipper.setDisplayedChild(i)),从而可以访问额外的视图。

现在所有的 Java 代码都包含在 Activity 主类中,这开始看起来很糟糕。作为一名经验丰富的 .NET 开发人员,我学会了如何使用自定义控件将布局和业务逻辑封装在模块内。

我知道在 Android 中,我可以通过编程将视图定义为独立类,并通过编程将其添加到主布局中,但是我想知道在 Android 中是否可以通过 XML 定义布局(便于所见即所得的创建/编辑)并在专用类中定义所有代码,包括初始化逻辑、按钮回调、异步任务等。

我不确定这是否可行,或者是否有一个很好的折衷方案。

我已经阅读了this 的问题,但没有消除我的疑虑。

谢谢。

代码示例:

布局文件的摘录(我预计需要 4 个向导步骤、一个帮助视图和一个 EULA 视图)

<?xml version="1.0" encoding="utf-8"?>
<ViewFlipper xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/view_phone"
    style="@android:style/Theme.Light.NoTitleBar"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <!-- First screen/welcome -->

    <LinearLayout
        android:id="@+id/view_phone_screen1"
        style="@android:style/Theme.Light.NoTitleBar"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical"
        android:weightSum="100" >

        <TextView
            android:id="@+id/view_phone_screen1_lblChooseProvider"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical|center_horizontal"
            android:text="@string/view_phone_lblChooseProvider_1ststep"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <ImageButton
            android:id="@+id/view_phone_btnFrecciarossa"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:clickable="true"
            android:contentDescription="@string/provider_FRECCIAROSSA"
            android:gravity="center_vertical|clip_vertical"
            android:padding="10dp"
            android:src="@drawable/logo_frecciarossa"
            android:tag="@+id/provider_FRECCIAROSSA" />

        <ImageButton
            android:id="@+id/view_phone_btnItalo"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:clickable="true"
            android:contentDescription="@string/provider_ITALO"
            android:gravity="center_vertical|clip_vertical"
            android:padding="10dp"
            android:src="@drawable/logo_italo"
            android:tag="@+id/provider_ITALO" />
    </LinearLayout>


    <!-- Second screen - will need to do some asynchronous task -->
    <RelativeLayout
        android:id="@+id/view_phone_screen2"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >

        <TextView
            android:id="@+id/view_phone_screen2_lblConnectingToWifi"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical|center_horizontal"
            android:text="@string/view_phone_lblConnectToWifi_2ndstep"
            android:textAppearance="?android:attr/textAppearanceLarge" />


        <TextView
            android:id="@+id/view_phone_step2_lblConnectedToWifi"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/imageView1"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="58dp"
            android:text="@string/view_phone_step2_connectingToWifi"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <TextView
            android:id="@+id/view_phone_step2_lblPhoneNumber"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignLeft="@+id/editText1"
            android:layout_below="@+id/view_phone_step2_lblConnectedToWifi"
            android:layout_marginTop="51dp"
            android:text="@string/view_phone_step2_msgInputPhoneNumber"
            android:textAppearance="?android:attr/textAppearanceMedium" />

        <TextView
            android:id="@+id/view_phone_step2_lblUnableDetectPhoneNumber"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:text="@string/view_phone_step2_msgUnableDetectPhoneNumber"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:visibility="invisible" />

        <Button
            android:id="@+id/view_phone_screen2_backward"
            style="@style/buttonBackward" />

        <Button
            android:id="@+id/view_phone_screen2_forward"
            style="@style/buttonForward_disabled"
            android:enabled="false" />

        <EditText
            android:id="@+id/view_phone_step2_txtPhoneNumber"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignRight="@+id/view_phone_step2_lblPhoneNumber"
            android:layout_below="@+id/view_phone_step2_lblPhoneNumber"
            android:inputType="phone"
            android:singleLine="true" >

            <requestFocus />
        </EditText>
    </RelativeLayout>

</ViewFlipper>

来自 Activity 的代码示例(期望实现 4+2 步向导的所有逻辑)

public class MyActivity extends Activity {
/** Called when the activity is first created. */

private final static String LOG_TAG = "LOG_TAG";
private int stepNumber;

@Override
public void onCreate(Bundle savedInstanceState) {
    this.requestWindowFeature(Window.FEATURE_NO_TITLE);
    this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

    super.onCreate(savedInstanceState);

    this.stepNumber=1;

    setContentView(R.layout.view_phone);

    //This class wraps the click for the two buttons
    ProviderSelectionListener providerSelectionListener = new ProviderSelectionListener(this);
    this.findViewById(R.id.view_phone_btnFrecciarossa).setOnClickListener(providerSelectionListener);
    this.findViewById(R.id.view_phone_btnItalo).setOnClickListener(providerSelectionListener);
}

@Override
protected void onPause() {
    super.onPause();

    try {
        if (MyApplication.getPlatformManager() != null)
            MyApplication.getPlatformManager().onApplicationPause();
    } catch (MyCustomException e) {
        // WTF (Worse Than Failure!)
        Log.e(LOG_TAG, super.getString(R.string.zf_error_unknown_error_pauseactivity), e);
        e.printStackTrace();
    }
}

@Override
protected void onResume() {
    super.onResume();

    try {
        if (MyApplication.getPlatformManager() != null)
            MyApplication.getPlatformManager().onApplicationResume();
    } catch (MyCustomException e) {
        // WTF (Worse Than Failure!)
        Log.e(LOG_TAG, super.getString(R.string.zf_error_unknown_error_pauseactivity), e);
        e.printStackTrace();
    }
}

/*
 * SLIDE INIZIO
 */
protected void slideNext() {
    ViewFlipper vf = (ViewFlipper) findViewById(R.id.view_phone);

    vf.setOutAnimation(getApplicationContext(), R.anim.slide_out_left);
    vf.setInAnimation(getApplicationContext(), R.anim.slide_in_right);
    vf.showNext();

}

protected void slidePrevious() {
    ViewFlipper vf = (ViewFlipper) findViewById(R.id.view_phone);

    vf.setOutAnimation(getApplicationContext(), R.anim.slide_out_right);
    vf.setInAnimation(getApplicationContext(), R.anim.slide_in_left);
    vf.showPrevious();
}

/*
 * SLIDE FINE
 */

/*
 * STEP 1 INIZIO
 */
public void completeStep1(ISmsWifiProvider provider) {
    if (provider == null) {
        Log.e(LOG_TAG, "Provider nullo");
        return;
    }

    MyApplication.setAuthenticationProvider(provider);

    slideNext();

    initializeStep2();
}

public void returnToStep1() {
    MyApplication.setAuthenticationProvider(null);
    slidePrevious();
}

/*
 * STEP 1 FINE
 */

/*
 * STEP 2 INIZIO
 */

private void initializeStep2() {
    // Event handler
    Button backButton = (Button) findViewById(R.id.view_phone_screen2_backward), fwButton = (Button) findViewById(R.id.view_phone_screen2_forward);
    fwButton.setEnabled(false);
    backButton.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            returnToStep1();
        }
    });

}

/*
 * STEP 2 FINE
 */


@Override
public void onBackPressed() {
    // This will be called either automatically for you on 2.0
    // or later, or by the code above on earlier versions of the
    // platform.
    return;
}

}

【问题讨论】:

  • 我认为这只是重构一点代码的问题......它是否过于“私人”?我的意思是,如果你分享你的代码,我们会有更多的想法来帮助你。
  • 这不是隐私问题,而是复杂性问题。无论如何我会分享一点(仍在进行中)
  • OK 我刚刚发现了“include”标签,以便在多视图布局中更轻松地模块化 XML 布局。重构那个代码
  • 多个屏幕可以/应该在不同的活动/片段中实现。这也导致每个类的代码更少,并且应该适用于向导(特别是如果您只想前进和后退)
  • blog.blundell-apps.com/animate-an-activity我猜你可以自定义在activity之间切换时发生的动画。

标签: android code-behind


【解决方案1】:

我想知道在 Android 中是否可以通过 XML 定义布局(以便于所见即所得的创建/编辑)并在专用类中定义所有代码,包括初始化逻辑、按钮回调、异步任务等。

是的。这是创建自定义View 的技术之一。比如我有a custom ColorMixer widget in this project,可以直接用在activity中,也可以用在dialog中,也可以用在自定义首选项中。

我同意您通过ViewFlipper 实施向导的战术决定——请参阅this answer to another StackOverflow question 了解“墨菲的活动理论”。

我怀疑,从长远来看,正确的答案是让某人(可能是我)想出一个基于 Fragment 的向导模式,因为它可以为您提供所需的解耦。

【讨论】:

  • 不幸的是,这正是我不想要的。我知道如何以编程方式定义自定义视图,但我只想通过 XML 定义它们的外观以及它们在 Java 中的行为(单击、异步事件......)。我想要一个 XML + 一个 Java 类。也许如果我可以使用带有动画的多个视图,我可以取得很好的效果
  • @djechelon:“我只想通过 XML 定义它们的外观以及它们在 Java 中的行为(点击、异步事件......)。” ——这正是我要说的。
  • 也许我应该看看 ParcelHelper 类。 +1 :)
  • 您能否描述一下您是如何制作 ColorMixer 的,或者将我链接到教程?我认为关键是您的 ParcelHelper 类,但我相信我需要一些指导。
  • @djechelon:我在 The Busy Coder's Guide to Advanced Android Development 中介绍了ColorMixer。知识共享许可的旧版本之一有该章节:commonsware.com/AdvAndroid/AdvAndroid-1_9-CC.pdf
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-04
  • 2022-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-30
  • 2012-06-30
相关资源
最近更新 更多