[关闭]
@flyouting 2014-07-01T23:45:18.000000Z 字数 5298 阅读 7644

在Android中创建卡片式UI

Android card UI


自从Google首次在Google+中推出了卡片式UI,卡片式变得越来越受欢迎。每个人都喜欢这种样式,并在自己的软件里实现类似的概念。它不仅可用于时尚的图形展示,也提供相应的逻辑功能,每个卡片都有它自己的内容和作用于内容的行为逻辑。换句话说,每个卡片都有自己的内容管理。
此处输入图片的描述

当我们开始为易趣Kleinanzeigen程序考虑预定功能时,最大的问题就是这个功能的入口点应该放在哪里,最后我们决定最好的方式就是在用户广告列表采用卡片界面。这样,每个广告通过"Promote"按钮可以增强本身的作用。

在这片文章中,我将试着使用listview adapter来创建卡片式界面。

首先是布局

布局包含一个listview,外层包含一个LinearLayout,背景为灰色。Listview包含10dip的padding,10dip的透明分隔线,另外,列表项背景色为白色,这样他们能各自区分出来,更像是一个卡片。这里是布局代码:

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:background="@color/light_grey" >
  6. <ListView
  7. android:id="@+id/cards_list"
  8. android:layout_width="match_parent"
  9. android:layout_height="wrap_content"
  10. android:clipToPadding="false"
  11. android:divider="@android:color/transparent"
  12. android:dividerHeight="10dp"
  13. android:padding="10dp"
  14. android:scrollbarStyle="outsideOverlay"
  15. tools:listitem="@layout/list_item_card" />
  16. </LinearLayout>

有关ListView的一个细节实际上很重要,clipToPadding属性应该被设置成false(默认情况是TRUE),否则的话,Listview的内容区不会滚动到padding区域,下图可以直观的解释:
此处输入图片的描述

另一个重要的地方是scrollbarStyle属性,我们设置为"outsideOverlay"以使不出现叠加到卡片的情况,这出现在Listview的边缘,忽略padding。

列表项的设计源于我们的内容,在这里,我添加了一个文本,两个功能按钮,白色背景。

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. android:background="@drawable/selectable_background"
  5. android:orientation="vertical" >
  6. <TextView
  7. android:id="@+id/list_item_card_text"
  8. style="@style/ListItemText"
  9. android:layout_width="match_parent"
  10. android:layout_height="wrap_content" />
  11. <View
  12. android:id="@+id/list_item_seperator"
  13. android:layout_width="match_parent"
  14. android:layout_height="1dip"
  15. android:layout_marginLeft="5dip"
  16. android:layout_marginRight="5dip"
  17. android:background="@color/light_grey" />
  18. <LinearLayout
  19. style="?android:attr/buttonBarStyle"
  20. android:layout_width="match_parent"
  21. android:layout_height="wrap_content" >
  22. <Button
  23. android:id="@+id/list_item_card_button_1"
  24. style="?android:attr/buttonBarButtonStyle"
  25. android:layout_width="match_parent"
  26. android:layout_height="wrap_content"
  27. android:layout_margin="5dip"
  28. android:layout_weight="1"
  29. android:focusable="false"
  30. android:focusableInTouchMode="false"
  31. android:text="@string/list_item_left_button"
  32. android:textSize="12sp"
  33. android:textStyle="normal" />
  34. <Button
  35. android:id="@+id/list_item_card_button_2"
  36. style="?android:attr/buttonBarButtonStyle"
  37. android:layout_width="match_parent"
  38. android:layout_height="wrap_content"
  39. android:layout_margin="5dip"
  40. android:layout_weight="1"
  41. android:focusable="false"
  42. android:focusableInTouchMode="false"
  43. android:text="@string/list_item_right_button"
  44. android:textSize="12sp"
  45. android:textStyle="normal" />
  46. </LinearLayout>
  47. </LinearLayout>

回归代码

代码部分的设计也很简单,包含几个重要或者棘手的部分,我更喜欢先创建adapter以备Listview初始化时传入。我只是继承BaseAdapter,在getView方法里初始化列表项布局,初始化文本控件和功能按钮,重要的部分是如何给按钮设置点击监听事件,因为adapter采用回收机制,非常容易发生的是,如果一个OnClickListener被设置在adapter内部,下一个复用的列表项会使用完全相同的onClickListener,数据却是不同的,在适配器逻辑中,每个列表项都有同样的元素,同样的View Id,因此,不可能根据ID知道哪个列表项的按钮被点击了。

在这一点上,我们最好在将adapter设置到Listview的地方设置回调,要做到这一点,我在Adapter的构造函数里接收一个View.OnClickListener,保存为适配器的一个变量,并设置给按钮。

代码如下:

  1. public CardsAdapter(Context context, List<String> items, OnClickListener itemButtonClickListener) {
  2. this.context = context;
  3. this.items = items;
  4. this.itemButtonClickListener = itemButtonClickListener;
  5. }
  6. @Override
  7. public View getView(int position, View convertView, ViewGroup parent) {
  8. ViewHolder holder;
  9. if (convertView == null) {
  10. convertView = LayoutInflater.from(context).inflate(R.layout.list_item_card, null);
  11. holder = new ViewHolder();
  12. holder.itemText = (TextView) convertView.findViewById(R.id.list_item_card_text);
  13. holder.itemButton1 = (Button) convertView.findViewById(R.id.list_item_card_button_1);
  14. holder.itemButton2 = (Button) convertView.findViewById(R.id.list_item_card_button_2);
  15. convertView.setTag(holder);
  16. } else {
  17. holder = (ViewHolder) convertView.getTag();
  18. }
  19. holder.itemText.setText(items.get(position));
  20. if (itemButtonClickListener != null) {
  21. holder.itemButton1.setOnClickListener(itemButtonClickListener);
  22. holder.itemButton2.setOnClickListener(itemButtonClickListener);
  23. }
  24. return convertView;
  25. }

棘手的部分

在Fragment或者Activity中,我们初始化包含Listview的布局,初始化Listview实例,然后设置适配器,最后我们传一个View.OnclickListener对象到适配器的构造函数里,我们不需要去在意未显示的列表项,因为他们在窗口之外不可能被点击。

在View.OnClickListener中重写onClick方法,我简单的遍历当前可见的列表项,借助方法为getFirstVisiblePosition()和getLastVisiblePosition()。然后检查点击的按钮是否属于当前迭代的那一项。

  1. private final class ListItemButtonClickListener implements OnClickListener {
  2. @Override
  3. public void onClick(View v) {
  4. for (int i = cardsList.getFirstVisiblePosition(); i <= cardsList.getLastVisiblePosition(); i++) {
  5. if (v == cardsList.getChildAt(i - cardsList.getFirstVisiblePosition()).findViewById(R.id.list_item_card_button_1)) {
  6. // PERFORM AN ACTION WITH THE ITEM AT POSITION i
  7. Toast.makeText(getActivity(), "Clicked on Left Button of List Item " + i, Toast.LENGTH_SHORT).show();
  8. } else if (v == cardsList.getChildAt(i - cardsList.getFirstVisiblePosition()).findViewById(R.id.list_item_card_button_2)) {
  9. // PERFORM ANOTHER ACTION WITH THE ITEM AT POSITION i
  10. Toast.makeText(getActivity(), "Clicked on Right Button of List Item " + i, Toast.LENGTH_SHORT).show();
  11. }
  12. }
  13. }
  14. }

关键点在于当ListView.getFirstVisiblePosition()或ListView.getLastVisiblePosition()方法返回给我们列表内的精确索引时,ListView.getChildAt方法返回给我们在可见区域内的索引位置,例如,firstVisiblePosition是3,getChildAt(3)会返回列表的第6个元素。

另外,我添加了一个OnItemClickListener到ListView,用于当列表项的内容被点击时做一些事情,比如跳转到详情页。

结果

最后,我们得到一个简单的列表视图的外观和感觉的卡片UI。也许我们可以添加更多的元素。

这个示例的完整代码可以在GitHub上找到。

原文地址
翻译:flyouting

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注