Android UI:完整示例应用

本文是我们名为“ Android UI设计–基础 ”的学院课程的一部分。

在本课程中,您将了解Android UI设计的基础知识。 您将了解用户输入,视图和布局以及适配器和片段。 在这里查看

1.简介

在上一篇有关Android UI的文章中,我们将构建一个Android应用程序,该应用程序几乎使用了前几篇文章中讨论的所有概念。 我们讨论了开发Android应用程序时需要考虑的最重要方面。 我们看到了如何使用布局管理器创建UI结构以及如何放置小部件。 我们描述了开发应用程序时应使用的一些最佳做法。 好吧,我们将要构建的应用程序将基于前面介绍的主题,因此请再次查看它们以刷新您的记忆。

作为示例,我们将构建一个To Do app :这是一个简单的应用程序,我们可以在其中添加待办事项并进行管理。 我们将介绍如何创建UI布局结构,如何添加小部件以向用户显示文本消息以及如何接受用户输入。 我们将考虑的一个重要方面是如何构建可在具有不同屏幕尺寸和分辨率的多种设备上使用的应用程序。

2.应用程式结构

在深入研究代码细节之前,构建应用程序时首先要考虑的是绘制一些草图,以帮助我们了解应用程序导航和用户交互。 我们可以使用几种工具,其中一些是免费的。 此外,这些草图有助于我们了解我们的应用程序的外观,并可以向客户展示它们,以便他们了解我们要构建的应用程序是否尊重他们的需求。

回到我们的待办事项应用程序,我们可以为我们成像我们必须满足的这些要求:

  • 应该有一个项目清单(待办事项)。
  • 用户可以将一项添加到现有项。
  • 项目应具有优先级颜色。
  • 该应用程序应在智能手机和平板电脑上运行。

在真正的应用程序中,要求当然会复杂得多,但这只是一个垫脚石。 我们可以想象像这样的简单导航:

图1

图1

图2

图2

这是一个非常简单的导航:在启动时,该应用程序显示当前项目列表,并且当用户单击操作栏上的“添加项目”时。 该应用程序将显示“添加项目”屏幕。 为了使事情简单并专注于UI方面,我们可以假设该应用程序将不保存项目。 对于读者来说,扩展应用程序以保存项目可能是一个有趣的练习。

现在,我们大致了解了导航的内容以及应用程序用户界面的外观,我们可以开始使用IDE创建应用程序了。 在这种情况下,我们将使用Eclipse + ADT。 我们创建一个可以称为Todo的新Android项目。 我们不会介绍如何使用Eclipse创建Android项目,因此我们假定您已经熟悉此IDE。 如果您不熟悉此过程,请查看我们的“ Android Hello World”示例

具有ListView和对象模型的项目列表

现在我们有了项目结构,我们可以集中精力设计应用程序背后的模型。 在这种情况下,模型非常简单,它只是一个类,其中包含有关新待办事项的信息:

public class Item implements Serializable {
	private String name;	
	private String descr;
	private Date date;
	private String note;
	private TagEnum tag;
            // Set and get methods
}

这将是我们将在应用程序中处理的基本类。 在我们刚刚创建的Android项目中,我们注意到在layout目录下有一个默认布局,名为activity_main.xml 。 这是该工具创建的默认布局。

现在,我们可以假设在此布局中只有一个项目列表:该布局仅用于智能手机,稍后我们将在平板电脑上运行该应用程序时进行考虑。 列表项的布局非常简单,它是由标准ListView小部件构建的:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <ListView
        android:id="@+id/listItmes"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

如果您注意到了,我们指示Android只使用必要的空间来容纳列表中的项目。 我们知道要使用ListView我们必须实现一个适配器。 我们可以使用Android提供的标准适配器,但是在这种情况下,这些标准适配器还不够,我们要实现自定义适配器,因为我们希望显示列表中每一行的信息。 我们希望列表中的行如下所示:

图3

图3

您会注意到,每行的左侧都有一个图像,代表待办事项优先级和一些信息。 到目前为止,我们不考虑将任何样式应用于行。 要像这样在ListView有一行,我们必须创建一个行布局,该行布局将在自定义适配器中使用。 因此,我们可以在layout目录下创建一个名为item_layout.xml的新文件。 该文件如下所示:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView
        android:id="@+id/tagView"
        android:layout_width="30dp"
        android:layout_height="20dp"
        android:background="@color/red" />

    <TextView
        android:id="@+id/nameView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_toRightOf="@id/tagView" />

    <TextView
        android:id="@+id/descrView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/nameView"
        android:layout_toRightOf="@id/tagView" />

    <TextView
        android:id="@+id/dateView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true" />

</RelativeLayout>

在此布局中,我们使用RelativeLayout管理器轻松将小部件放置在所需位置。 如您在此布局管理器中所看到的,视图是根据其他视图位置放置的。 例如,我们希望将待办事项名称放置在图像之后,因此我们使用属性:

android:layout_toRightOf="@id/tagView"

此外,我们可以放置相对于父视图的视图,例如,我们希望将日期信息放置在行的右侧和底部:

android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"

现在我们有了布局,我们必须构建适配器。 我们将扩展ArrayAdapter并覆盖一些方法,以便我们可以处理模型数据和新布局。 我们称这个适配器为ToDoItemAdaper ,所以我们有:

public class ToDoItemAdapter extends ArrayAdapter<Item> {
	private Context ctx;
	private List<Item> itemList; 
	public ToDoItemAdapter(Context context, List<Item> itemList) {
		super(context, R.layout.item_layout);
		this.ctx = context;
		this.itemList = itemList;
	}
}

构造函数接收ContextitemList作为参数,最后一个参数保存待办事项列表。 您会注意到,当我们调用super方法时,会传递R.layout.item_layoutR.layout.item_layout自定义布局。 现在,我们必须重写最重要的方法之一,称为getView ,该方法用于创建View并呈现行布局:

@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		View v = convertView;
		ItemHolder h = null;
		if (v == null) {
			// Inflate row layout
			LayoutInflater inf = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			v = inf.inflate(R.layout.item_layout, parent, false);
			// Look for Views in the layout
			ImageView iv = (ImageView) v.findViewById(R.id.tagView);
			TextView nameTv = (TextView) v.findViewById(R.id.nameView);
			TextView descrView = (TextView) v.findViewById(R.id.descrView);
			TextView dateView = (TextView) v.findViewById(R.id.dateView);
			h = new ItemHolder();
			h.tagView = iv;
			h.nameView = nameTv;
			h.descrView = descrView;
			h.dateView = dateView;
			v.setTag(h);
		}
		else		
		   h = (ItemHolder) v.getTag();

		h.nameView.setText(itemList.get(position).getName());		
		h.descrView.setText(itemList.get(position).getDescr());
		h.tagView.setBackgroundResource(itemList.get(position).getTag().getTagColor());
		h.dateView.setText(sdf.format(itemList.get(position).getDate()));
		
		return v;
	}

在此方法中,我们首先检查我们作为参数接收的View是否为null。 在这种情况下,我们必须增加布局。 如果您注意到了,我们使用ViewHolder模式使ListView滚动更平滑。 我们创建了一个名为ItemHolder内部ItemHolder ,该类在我们的自定义布局中保存对View的引用:

// ViewHolder pattern
static class ItemHolder {
	ImageView tagView;
	TextView nameView;
	TextView descrView;
	TextView dateView;		
}

您应该注意的一件事是我们如何处理ImageView的背景色。 我们使用setBackgroundResource设置imageview背景。 该方法接受一个int表示我们要用作背景的资源ID:

h.tagView.setBackgroundResource(itemList.get(position).getTag().getTagColor());

查看我们的模型类,我们可以注意到getTag()方法返回TagEnum类的实例。 这是用这种方式定义的枚举:

public enum TagEnum {
	BLACK(R.color.black,"Black"), RED(R.color.red, "Red"), 
	GREEN(R.color.green, "Green"), BLUE(R.color.blue, "Blue"),YELLOW(R.color.yellow,"Yellow");
	private int code;
	private String name;
	private TagEnum(int code, String name) {
		this.code = code;
		this.name = name;
	}
	public int getTagColor() {
		return this.code;
	}
}

在枚举中,我们定义了我们要支持的不同颜色,并且作为第一个参数,我们传递了资源ID。 如果您还记得上一篇文章,我们讨论了如何以XML格式定义资源颜色。 我们已经知道,必须在res/values下创建一个XML文件,我们可以将其称为colors.xml

<resources>

    <color name="red" >#FF0000
    </color>

    <color name="green" >#00FF00
    </color>

    <color name="blue" >#0000FF
    </color>

    <color name="black" >#000000
    </color>

    <color name="yellow" >#FFAA00
    </color>

</resources>

在枚举颜色定义中,我们使用R.color.color_name引用了此颜色,因此,当在自定义适配器getView使用getTagColor方法时,我们将收到图像背景将使用的资源ID。 要理解的一个重要方面是,我们没有在构造函数中对颜色进行硬编码:例如,我们可以直接将颜色十六进制代码(如#FF0000)用于红色,依此类推。

即使结果相同,也不建议在源代码中使用硬编码的值。 例如,如果我们想将红色更改为另一种颜色,则必须在源代码中找到十六进制颜色并进行更改,但是如果我们使用了资源来定义颜色,我们将直接转到保存该颜色的文件。颜色定义并更改我们喜欢的颜色。

请注意,在枚举中,我们使用了一种错误的做法:我们直接编写颜色名称。 我们故意使用它来向您展示您不应做的事情。 在这种情况下,如果要支持多语言应用程序,则必须更改使用字符串资源文件中写入的名称初始化枚举的方式。

多设备支持和布局注意事项

请记住,我们的要求之一是我们必须构建一个同时支持智能手机和平板电脑的应用程序。 考虑平板电脑屏幕的尺寸,我们意识到屏幕太大,无法容纳一个项目列表,因此我们可以考虑将屏幕分为两个区域:一个容纳列表,另一个可以用于显示项目详细信息,或者甚至显示用户界面以添加新项目。 如果使用平板电脑,这是正确的,但是如果我们使用智能手机,则屏幕尺寸不足以将其分为两个区域。

同时,我们不想开发两个不同的代码分支:一个用于智能手机,一个用于平板电脑。 我们将重写相同的代码,只是更改一些细节和尺寸。 Android帮助我们解决了这个问题:我们在上一篇文章中讨论了Fragment 。 因此,我们可以创建一个片段来处理用户界面,以将新项目添加到列表中。 片段封装了一组组件和活动行为,因此我们可以在不同的活动中重用这段代码。 下图描述了我们必须处理的情况:

图4

图4

图5

图5

当应用程序在智能手机中运行时,我们必须处理两项活动,一项针对列表项,另一项处理用户输入,而在平板电脑中,我们只能进行一项活动。

我们可以假设屏幕大小至少为600dp,因此我们希望将屏幕划分为不同的区域,并在res/layout-sw600dp下定义新的布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <ListView
        android:id="@+id/listItmes"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

    <FrameLayout
        android:id="@+id/frm1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

</LinearLayout>

其中的FrameLayout将根据导航流程动态“填充”。

当然,根据屏幕尺寸的不同,我们可以更详细地自定义布局。 在这种情况下,我们可以简单地在res下的不同目录中创建不同的布局。

添加项目用户界面布局

如果我们运行该应用程序,结果将是一个没有项目的空列表。 我们必须创建一个新的用户界面。 因此,请记住我们之前所做的考虑,我们创建一个用于处理添加项功能的Fragment ,将其称为NewItemFragment 。 片段具有复杂的生命周期,但为此,我们可以仅覆盖onCreateView方法。 此方法负责创建用户界面。 与往常一样,我们必须首先创建布局。 在我们的IDE的res/layout我们创建另一个xml文件,我们将其称为add_item_layout.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:id="@+id/txtTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:text="@string/addItem" />

    <TextView
        android:id="@+id/itemName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/txtTitle"
        android:layout_marginStart="10dp"
        android:text="@string/addItemName" />

    <EditText
        android:id="@+id/edtName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/itemName"
        android:hint="@string/addItemNameHint" />

    <TextView
        android:id="@+id/itemDescr"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/edtName"
        android:layout_marginTop="10dp"
        android:text="@string/addItemDescr" />

    <EditText
        android:id="@+id/edtDescr"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/itemDescr"
        android:hint="@string/addItemDescrHint" />

    <TextView
        android:id="@+id/itemNote"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/edtDescr"
        android:layout_marginTop="10dp"
        android:text="@string/addItemNote" />

    <EditText
        android:id="@+id/edtNote"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/itemNote"
        android:hint="@string/addItemNoteHint" />

    <TextView
        android:id="@+id/itemDate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/edtNote"
        android:layout_marginTop="10dp"
        android:text="@string/addItemDate" />

    <TextView
        android:id="@+id/inDate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/itemDate"
        android:layout_marginTop="10dp" />

    <TextView
        android:id="@+id/itemTime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/inDate"
        android:layout_marginTop="10dp"
        android:text="@string/addItemTime" />

    <TextView
        android:id="@+id/inTime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/itemTime"
        android:layout_marginTop="10dp" />

    <TextView
        android:id="@+id/itemTag"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/inTime"
        android:layout_marginTop="10dp"
        android:text="@string/addItemTag" />

    <Spinner
        android:id="@+id/tagSpinner"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/itemTag" />

    <!-- ADD button -->

    <Button
        android:id="@+id/addBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="@string/addButton" />

</RelativeLayout>

这是一个非常简单的布局,由TextViewEditText :第一个用于在用户界面上显示文本消息,另一个用于接受用户输入。 在此布局中有两个重要组成部分: SpinnerDate/Time picker

微调框是一个UI组件,它一次仅显示一项,并允许用户从中选择一项。 我们使用此组件显示不同的标签颜色/优先级。 它完全符合我们的目的,实际上,我们希望用户在颜色列表中选择一种颜色。

标签颜色/优先旋转器

为了正常工作, Spinner需要在其后面有阵列适配器。 Android提供了我们可以使用的适配器列表,但是我们希望自定义它们,因为我们希望显示带有颜色的图像。 然后,我们必须以与ListView相同的方式创建一个自定义适配器。 首先,我们创建名为spinner_tag_layout.xml的行布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView
        android:id="@+id/tagSpinnerImage"
        android:layout_width="30dp"
        android:layout_height="20dp" />

    <TextView
        android:id="@+id/tagNameSpinner"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/tagSpinnerImage" />

</RelativeLayout>

最后我们创建适配器:

public class TagSpinnerAdapter extends ArrayAdapter<TagEnum> {

	private Context ctx;
	private List<TagEnum> tagList;
	
	public TagSpinnerAdapter(Context ctx, List<TagEnum> tagList) {
		super(ctx, R.layout.spinner_tag_layout);
		this.ctx = ctx;
		this.tagList = tagList;
	}

	
	@Override
	public View getDropDownView(int position, View convertView, ViewGroup parent) {
		return _getView(position, convertView, parent);
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		return _getView(position, convertView, parent);
	}

	private View _getView(int position, View convertView, ViewGroup parent) {
		View v = convertView;
		if (v == null) {
			// Inflate spinner layout
			LayoutInflater inf = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			v = inf.inflate(R.layout.spinner_tag_layout, parent, false);
		}
		
		// We should use ViewHolder pattern
		ImageView iv = (ImageView) v.findViewById(R.id.tagSpinnerImage);
		TextView tv = (TextView) v.findViewById(R.id.tagNameSpinner);
		
		TagEnum t = tagList.get(position);
		iv.setBackgroundResource(t.getTagColor());
		tv.setText(t.getName());
		return v;
	}

}

通过分析上面的代码,我们注意到该适配器处理TagEnum对象,并且覆盖了两种方法getViewgetDropDownView 。 我们以相同的方式处理这些方法。 如您所见,我们对ListView所做的几乎相同的操作。

在包含此UI组件的片段中,我们必须找到对Spinner的引用并设置上面定义的自定义布局:

Spinner sp = (Spinner) v.findViewById(R.id.tagSpinner);
TagSpinnerAdapter tsa = new TagSpinnerAdapter(getActivity(), tagList);
sp.setAdapter(tsa);

当用户在Spinner选择一项时,我们必须找到一种方法来知道选择了哪一项。

如您所记得,当组件中发生某些事件时,我们可以使用侦听器来通知。 在这种情况下,我们对项目选择事件感兴趣,因此我们为其创建一个侦听器:

sp.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
	@Override
	public void onItemSelected(AdapterView<?> adptView, View view,
		int pos, long id) {
		currentTag = (TagEnum) adptView.getItemAtPosition(pos);
	}

	@Override
	public void onNothingSelected(AdapterView<?> arg0) {
		// TODO Auto-generated method stub
				
		}
	});

并将结果存储在class属性中。

日期和时间选择器

当我们向列表中添加新的待办事项时,我们希望用户选择日期和时间。 Android提供了两个组件,分别称为DatePickerDialogTimePickerDialog 。 顾名思义,这是两个可以打开以选择日期和时间的对话框。

我们正在使用片段,因此我们必须创建两个内部类,向用户呈现日期和时间选择器。 在这种情况下,我们为两个选择器都扩展了DialogFragment类,并覆盖了onCreateDialog方法。 在此方法中,我们只需初始化Date选择器并将其作为结果返回:

public static class DatePickerFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener {
	@Override
	    public Dialog onCreateDialog(Bundle savedInstanceState) {
	        // Use the current date as the default date in the picker
	        final Calendar c = Calendar.getInstance();
	        c.setTime(selDate);
	        int year = c.get(Calendar.YEAR);
	        int month = c.get(Calendar.MONTH);
	        int day = c.get(Calendar.DAY_OF_MONTH);

	        // Create a new instance of DatePickerDialog and return it
	        return new DatePickerDialog(getActivity(), this, year, month, day);
	    }
		
	@Override
	public void onDateSet(DatePicker view, int year, int monthOfYear,
				int dayOfMonth) {
			
		Calendar c = Calendar.getInstance();
		c.set(year, monthOfYear, dayOfMonth, 9, 0);
		selDate = c.getTime();
		tvDate.setText(sdfDate.format(selDate));
	}
}

从上面的代码中,您可以注意到我们只是将当前日期设置为onCreateDialogDatePickerDialog 。 我们实现了DatePickerDialog.OnDateSetListener ,以便在用户选择日期时得到通知。 在此接口的回调方法中,我们仅存储用户选择的日期。

以同样的方式,我们处理TimePickerFragment ,但是在这种情况下,我们扩展了TimePickerDialog

public static class TimePickerFragment extends DialogFragment implements TimePickerDialog.OnTimeSetListener {
	@Override
	 public Dialog onCreateDialog(Bundle savedInstanceState) {
	        // Use the current date as the default date in the picker
	        final Calendar c = Calendar.getInstance();
	        c.setTime(selDate);
	        int hour = c.get(Calendar.HOUR_OF_DAY);
	        int minute = c.get(Calendar.MINUTE);
	    // Create a new instance of TimePickerDialog and return it
	     return new TimePickerDialog(getActivity(), this, hour, minute,
		                DateFormat.is24HourFormat(getActivity()));
            }

          @Override
          public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
		Calendar c = Calendar.getInstance();
		c.setTime(selDate);
		c.set(Calendar.HOUR_OF_DAY, hourOfDay);
		c.set(Calendar.MINUTE, minute);	
		selDate = c.getTime();
		// We set the hour
		tvTime.setText(sdfTime.format(selDate));
	}
}

这是两个对话框,它们不会自己出现,但是我们必须检查用户是否单击日期/时间文本视图以打开这些对话框,所以我们有:

tvDate = (TextView) v.findViewById(R.id.inDate);
tvTime = (TextView) v.findViewById(R.id.inTime);

获取对TextView的引用,然后我们只需实现一个侦听器:

tvDate.setOnClickListener(new View.OnClickListener() {
	@Override
	public void onClick(View v) {
		DatePickerFragment dpf = new DatePickerFragment();
		dpf.show(getFragmentManager(), "datepicker");
	}
});

结果,我们有:

图6

图6

开发此片段的最后一部分专用于在用户单击添加按钮时处理事件。 发生此事件时,片段应创建一个Item类的实例,该实例保存用户插入的数据,并将其发送回保存该片段的活动。 最佳实践建议使用接口和回调方法。

我们可以在片段类中定义此接口:

public interface AddItemListener {
	public void onAddItem(Item item);
}

这是一个非常简单的界面,仅通过一种方法即可完成。 现在我们有:

Button addBtn = (Button) v.findViewById(R.id.addBtn);
addBtn.setOnClickListener(new View.OnClickListener() {
	@Override
	public void onClick(View v) {
		// We retrieve data inserted
		Item i = new Item();
		i.setName(edtName.getText().toString());
		i.setDescr(edtDescr.getText().toString());
		i.setNote(edtNote.getText().toString());
		i.setTag(currentTag);
		i.setDate(selDate);
		// Safe cast
		( (AddItemListener) getActivity()).onAddItem(i);
	}
});

通过使用接口,我们可以将片段与保存该片段的活动分离,这将非常有用,我们将在后面看到。

回到关于智能手机和平板电脑的注释并记住上面显示的图片,我们知道,如果应用程序在智能手机中运行,我们必须使用两个活动,因此我们创建另一个名为NewItemActivity ,该活动将保存上述片段。 此活动非常简单,因为它充当片段容器。

public class NewItemActivity extends Activity implements NewItemFragment.AddItemListener{
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		NewItemFragment nif = new NewItemFragment();
		getFragmentManager().beginTransaction().add(android.R.id.content, nif).commit();
	}

	@Override
	public void onAddItem(Item item) {
		// We get the item and return to the main activity
		Log.d("TODO", "onAddItem");
		Intent i = new Intent();
		i.putExtra("item", item);
		setResult(RESULT_OK,i);
		finish();
	}
}

onCreate方法中,我们创建片段类的新实例,并使用FragmentManager在屏幕上显示片段。

注意,此活动实现了片段中定义的接口,因为在用户想要添加新项目时必须通知该接口,因此它实现了onAddItem方法。 稍后我们将介绍此方法的功能。

主要活动

主要活动是应用程序的心脏,是启动时调用的活动。 它设置了初始布局,并显示了我们先前描述的项目列表:

itemListView = (ListView) findViewById(R.id.listItmes);
adpt = new ToDoItemAdapter(this, itemList);
itemListView.setAdapter(adpt);

现在,我们必须检查我们的应用程序是否在智能手机或平板电脑上运行,以便我们可以更改活动行为。 我们可以通过验证布局定义中是否存在FrameLayout组件来做到这一点:

if (findViewById(R.id.frm1) != null) 
   isTablet = true;

动作栏

在本活动中,我们添加带有操作的操作栏(著名的Android模式):添加新项目。 要定义它,我们在res/menu下创建(如果不存在)一个XML文件:

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

    <item
        android:id="@+id/action_add"
        android:icon="@android:drawable/ic_menu_add"
        android:orderInCategory="0"
        android:showAsAction="always"/>

</menu>

结果,我们获得:

图7

图7

当用户单击加号时,我们应该显示用于添加新项目的用户界面。 我们必须处理的第一件事是用户单击“加号”图标时发生的事件,因此我们必须覆盖活动类中的方法:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
	int menuId = item.getItemId();
	switch (menuId) {
		case R.id.action_add: {
			if (!isTablet) {
				Intent i = new Intent(this, NewItemActivity.class);
				startActivityForResult(i, ADD_ITEM);
				break;
			}
			else {
				Log.d("TODO", "Tablet");
				FragmentTransaction ft = getFragmentManager().beginTransaction();
				NewItemFragment nif = new NewItemFragment();
				ft.replace(R.id.frm1, nif);
				ft.commit();
			}
		}
	}
}

该方法非常重要,因为我们定义了活动的行为方式。 如果用户单击的按钮是我们的添加按钮,则我们首先要验证我们的应用程序是否在智能手机上运行。 在这种情况下,我们知道我们必须开始另一个活动,然后开始等待结果。

如果我们在调用的活动中启动一个活动以等待其结果,则应返回结果,并以这种方式在NewItemActivity中进行操作:

@Override
public void onAddItem(Item item) {
	// We get the item and return to the main activity
	Log.d("TODO", "onAddItem");
	Intent i = new Intent();
	i.putExtra("item", item);
	setResult(RESULT_OK,i);
	finish();
}

在此方法中,我们创建一个保存结果的Intent,并将其传递回调用活动( MainActivity ),然后完成该活动。 然后,在MainActivity ,我们必须准备好处理结果:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
	Log.d("TODO", "OnResult");
	if (requestCode == ADD_ITEM) {
		if (resultCode == RESULT_OK) {
			Log.d("TODO", "OK");
			Item i = (Item) data.getExtras().getSerializable("item");
			itemList.add(i);
			adpt.notifyDataSetChanged();
		}
	}
}

在此活动中,我们提取存储在作为结果接收到的Intent中的item对象,并将其添加到item列表中。 完成后,我们将调用notifyDataSetChange方法,以便可以更新ListView

如果我们的应用程序在平板电脑上运行,我们只需用上述NewItemFragment “填充” FrameLayout 。 在这种情况下,我们开始事务并用片段替换FrameLayout ,最后我们提交事务。 在这种情况下,我们不需要启动其他活动,因为我们使用FrameLayout来显示处理用于添加新项目的用户界面的片段。 因此,我们有:

图8

图8

在这种情况下,我们必须像以前一样简单地实现片段指定的接口:

@Override
public void onAddItem(Item item) {
	itemList.add(item);
	adpt.notifyDataSetChanged();
	NewItemFragment nif = (NewItemFragment) getFragmentManager().findFragmentById(R.id.frm1);
	getFragmentManager().beginTransaction().remove(nif).commit();
}

请注意,在最后一部分中,我们在最后删除了该片段。

3.样式化应用程序

到目前为止,我们尚未考虑应用程序的样式,但是我们知道可以将样式应用于每个UI组件。 我们可以修改所有UI的颜色和外观,实现我们的样式并为应用添加品牌。 例如,我们想将样式应用于listView行,使它们更具吸引力。 我们创建(如果尚不存在)在res/values下的一个名为style.xml的文件,在这里我们可以按照上一篇文章中的讨论定义样式:

<style name="dateStyle">
        <item name="android:textAppearance">?android:textAppearanceSmall</item>
        <item name="android:textColor">@color/red</item>
    </style>

     <style name="descrStyle">
        <item name="android:textStyle">italic</item>
        <item name="android:textAppearance">?android:textAppearanceSmall</item>
    </style>
    
     <style name="nameStyle">
         <item name="android:textAppearance">?android:textAppearanceMedium</item>
         <item name="android:textStyle">bold</item>
     </style>

我们定义了三种不同的样式,我们希望将它们应用于列表视图行:

<TextView
    android:id="@+id/nameView"
    style="@style/nameStyle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentTop="true"
    android:layout_toRightOf="@id/tagView" />
<TextView
    android:id="@+id/descrView"
    style="@style/descrStyle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@id/nameView"
    android:layout_toRightOf="@id/tagView" />
<TextView
    android:id="@+id/dateView"
    style="@style/dateStyle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_alignParentRight="true" />

以这种风格运行应用程序,我们可以:

图9

图9

4。结论

在本Android UI课程中,我们介绍了开发Android应用程序时应遵循的一些重要的UI方面,概念和最佳实践。 在上一篇文章中,我们看到了如何利用前几篇文章中涵盖的所有主题,将所有这些知识应用到构建真正的应用程序中。 当然,可以改进此简单的应用程序,您可以将其作为练习来尝试添加新功能。 例如,我们没有介绍如何修改或删除列表中的项目。 在这种情况下,我们可以在用户单击某个项目时进行监听,并且可以显示带有多个选项的菜单,或者可以使用操作栏根据用户操作对其进行更改。 例如,我们可以创建一个多选列表视图,当用户单击操作栏中的“垃圾箱”图标时,我们将删除所有选中的项目。

我们可以在此应用程序中进行不同方面的改进,如果您想深入了解Android开发方面的内容,这将是一个不错的练习。

5.下载源代码

这是关于如何从头开始创建Android应用程序UI的课程。 您可以在此处下载源代码: AndroidApp.zip

翻译自: https://www.javacodegeeks.com/2015/09/android-ui-full-sample-app.html

1、Android显示GIF动画 GifView GifView 是一个为了解决android中现在没有直接显示gif的view,只能通过mediaplay来显示这个问题的项目,其用法和 ImageView一样,支持gif图片 使用方法:1-把GifView.jar加入你的项目。2-在xml中配置GifView的基本属性,GifView继承自View类,和Button、ImageView一样是一个UI控件。 如: 3-在代码中配置常用属性: // 从xml中得到GifView的句柄 gf1 = (GifView) findViewById(R.id.gif1); // 设置Gif图片源 gf1.setGifImage(R.drawable.gif1); // 添加监听器 gf1.setOnClickListener(this); // 设置显示的大小,拉伸或者压缩 gf1.setShowDimension(300, 300); // 设置加载方式:先加载后显示、边加载边显示、只显示第一帧再显示 gf1.setGifImageType(GifImageType.COVER); GifView的Jar包共有四个类: GifAction.java 观察者类,监视GIF是否加载成功 GifFrame.java 里面三个成员:当前图片、延时、下张Frame的链接。 GifDecoder.java 解码线程类 GifView.java 主类,包括常用方法,如GifView构造方法、设置图片源、延迟、绘制等。 2、Calendar.v0.5.0 是 Android 平台的一个日历显示组件。 3、CWAC EndlessAdapter 是 Android 上一个可以无限往下滑进行列表数据加载的控件。 4、Android Horizontal ListView 是 Android 上一个水平滑动的 ListView 组件。 5、Android ViewBadger 视图布局。 6、滑动刷新的ListView Android PullToRefresh 为 Android 应用提供一个向下滑动即刷新列表的功能,就两个目标文件。 7、pakerfeldt-android-viewflow 是 Android 平台上一个视图切换的效果库。ViewFlow 相当于 Android UI 部件提供水平滚动的 ViewGroup,使用 Adapter 进行条目绑定。 8、Android 导航菜单 RibbonMenu 是 Android 上的一个导航菜单组件。就三个目标文件,菜单项直接在 XML 中定义,可添加文本和图标。 9、AndroidUI工具包 android-ui-utils 是一个工具包用来帮助设计和开发 Android 用户界面,包含三个单独的工具:Android Asset Studio用户界面原型模具,Android 设计预览,时常需要重复确认程序版面设计状况的 Android App 开发者,应该会爱上这个轻量级的 Java 程序:Andorid Design Preview 工具,通过 USB 连接之后,只要简单的在计算机中选取您想要显示的程序版面范围,就可将镜像结果直接显示于手机装置之上。 10、Androidui开发类库 GreenDroid 是一个Androidui开发类库,能够使你的Android开发更加简便和快捷。 11、Android滑动式菜单 SlidingMenu 是 Android 上实现类似 Facebook 和 Path 2.0 滑动式菜单的组件。 12、AsyncImageView 是 Android 上的一个异步从网络上获取图片并进行浏览的开源组件,可自动在本地进行缓存。该项目是 GreenDroid 的一部分。 13、仿Path按钮动画效果 PathButton 仿照Path应用首页左下角的Button动画效果写了个简单的Demo,由于数学不好,坐标总是和理想有出入,只是大致实现了动画效果,若果有人能把坐标算对,那么修改我的demo就能做成类似Path的那种动画效果!希望大家出点力帮着优化一下,并分享出来! 14、Android Intent开发包 OpenIntents Ope
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值