2014-10-07 96 views
2

我已经在我的listView中实现了ViewHolder和convertView。 我的listView由一个自定义适配器填充,并带有一个预订列表。 当我点击一个项目,一个不可见的布局从右到左滑动,以显示按钮。 我可以通过点击一个关闭按钮来消除这种重叠布局,以便它再次隐藏。 在这个覆盖布局上,我有一个删除按钮,它使我能够删除该项目。 目前为止这么好。 当我删除一个项目时,该项目按预期消失,然后重新加载适配器。 下面的项目采取已删除项目的位置,但仍然不可见。 我知道它在这里,因为我仍然可以点击该项目来触发覆盖视图。 因此,过度视图是可见的,但不是项目。我不知道为什么会发生这种情况。 我怀疑ViewHolder对此行为负责,但我找不到解决方案。 谢谢你的帮助。ViewHolder搞乱视图

查看视频在这里:http://youtu.be/KBGEvbUq-V0

我的预订类:

public class BookingsListFragment extends Fragment { 

private final String SHOP_NAME_KEY = "ShopName"; 
private final String SHOP_ADDRESS_KEY = "ShopAddress"; 
public static int mSelectedItem = -1; 
private static ListView mBookingsListView; 
private static BookingsListViewAdapter mBookingsListViewAdapter; 
private static ArrayList<Booking> mBookings; 



@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    ImageLoader.getInstance().init(ImageLoaderConfiguration.createDefault(getActivity())); 
} 

@Override 
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 
    View view = inflater.inflate(R.layout.bookings_list_fragment, container, false); 
    configureListView(view); 
    return view; 
} 

@Override 
public void onResume() { 
    super.onResume(); 
    mSelectedItem = -1; 
} 

private void configureListView(View view) { 
    mBookings = BookingsHandler.getBookings(); 
    mBookingsListView = (ListView) view.findViewById(R.id.bookingsListView); 
    mBookingsListViewAdapter = new BookingsListViewAdapter(); 
    mBookingsListView.setAdapter(mBookingsListViewAdapter); 
    mBookingsListView.setTextFilterEnabled(true); 
} 

public static void updateBookingsListView(ArrayList<Booking> mBookingsList){ 
    mBookings = mBookingsList; 
    mBookingsListViewAdapter.notifyDataSetChanged(); 
} 


static class ViewHolder { 
    LinearLayout bookingItemLL; 
    RelativeLayout optionsOverlay; 
    TextView productName; 
    TextView price; 
    TextView shopName; 
    TextView endDate; 
    ImageView productImage; 
    LinearLayout placeholderLL; 
    Button cancelBooking; 
    Button displayDirections; 
    Button callShop; 
    ImageView discardOverlay; 
} 


private class BookingsListViewAdapter extends BaseAdapter { 

    private static final int TYPE_ITEM = 0; 
    private static final int TYPE_PLACEHOLDER = 1; 

    @Override 
    public int getCount() { 
     if (mBookings != null) 
      return mBookings.size(); 
     else 
      return 1; 
    } 

    @Override 
    public Object getItem(int position) { 
     return position; 
    } 

    @Override 
    public long getItemId(int position) { 
     return position; 
    } 

    @Override 
    public int getItemViewType(int position) { 
     // Define a way to determine which layout to use 
     if (mBookings != null && mBookings.size() > 0) 
      return TYPE_ITEM; 
     else 
      return TYPE_PLACEHOLDER; 
    } 

    @Override 
    public int getViewTypeCount() { 
     return 2; // Number of different layouts 
    } 

    @Override 
    public View getView(final int position, View convertView, ViewGroup viewGroup) { 

     int type = getItemViewType(position); 

     final ViewHolder holder; 

     if(convertView == null) { 
      holder = new ViewHolder(); 

      switch (type){ 
       case TYPE_ITEM : 
        convertView = LayoutInflater.from(getActivity()).inflate(R.layout.bookings_item,  null); 

        holder.bookingItemLL = (LinearLayout) convertView.findViewById(R.id.bookingItemLL); 
        holder.optionsOverlay = (RelativeLayout) convertView.findViewById(R.id.bookingOptionsOverlay); 
        holder.productName = (TextView) convertView.findViewById(R.id.bookingProductName); 
        holder.price = (TextView) convertView.findViewById(R.id.bookedProductPrice); 
        holder.shopName = (TextView) convertView.findViewById(R.id.bookingShopName); 
        holder.endDate = (TextView) convertView.findViewById(R.id.bookingEndDate); 
        holder.productImage = (ImageView) convertView.findViewById(R.id.bookedProductImage); 
        holder.displayDirections = (Button) convertView.findViewById(R.id.routeShop); 
        holder.cancelBooking = (Button) convertView.findViewById(R.id.cancelBooking); 
        holder.callShop = (Button) convertView.findViewById(R.id.callShop); 
        holder.discardOverlay = (ImageView) convertView.findViewById(R.id.discardOverlay); 

        break; 
       case TYPE_PLACEHOLDER : 
        convertView = LayoutInflater.from(getActivity()).inflate(R.layout.booking_placeholder, null); 
        holder.placeholderLL = (LinearLayout) convertView.findViewById(R.id.placeHolderLL); 
        break; 
      } 
      convertView.setTag(holder); 
     } else { 
      holder = (ViewHolder)convertView.getTag(); 
     } 

     if(type == 0) { 

      if(position == mSelectedItem){ 
       holder.optionsOverlay.setVisibility(View.VISIBLE); 
       configureOverlayButtons(holder); 
      } 

      holder.bookingItemLL.setOnClickListener(new View.OnClickListener() { 
       @Override 
       public void onClick(View v) { 
        if(mSelectedItem != position && mSelectedItem != -1){ 
         View item = mBookingsListView.getChildAt(mSelectedItem - mBookingsListView.getFirstVisiblePosition()); 
         if(item != null){ 
          RelativeLayout overlayOptions = (RelativeLayout) item.findViewById(R.id.bookingOptionsOverlay); 
          overlayOptions.setVisibility(View.GONE); 
         } 
        } 
        Animation slideInAnimation = AnimationUtils.loadAnimation(getActivity(), R.anim.booking_options_overlay_animation); 
        holder.optionsOverlay.startAnimation(slideInAnimation); 
        holder.optionsOverlay.setVisibility(View.VISIBLE); 
        mSelectedItem = position; 
        configureOverlayButtons(holder); 
       } 
      }); 

      final Booking booking = mBookings.get(position); 
      holder.productName.setText(booking.getName().toUpperCase()); 
      holder.price.setText("Prix lors de la réservation : " + String.format("%.2f", Float.valueOf(booking.getPrice())) + " €"); 
      holder.shopName.setText(booking.getShopName()); 
      holder.endDate.setText(booking.getEndDate()); 
      holder.productImage.setScaleType(ImageView.ScaleType.CENTER_CROP); 

      DisplayImageOptions options = new DisplayImageOptions.Builder() 
        .showImageOnLoading(R.drawable.product_placeholder) 
        .showImageOnFail(R.drawable.product_no_image_placeholder) 
        .cacheInMemory(true) 
        .cacheOnDisk(true) 
        .build(); 
      ImageLoader imageLoader = ImageLoader.getInstance(); 
      imageLoader.displayImage(BeeWylApiClient.getImageUrl(booking.getImageURL()),holder.productImage, options); 
     } 
     if(type == 1){ 
      holder.placeholderLL.setLayoutParams(BeeWylHelper.getPlaceHolderSizeForFreeScreenSpace(getActivity(),0)); 
     } 
     return convertView; 
    } 


    private void configureOverlayButtons(final ViewHolder holder){ 

     holder.cancelBooking.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       AlertDialog.Builder ab = new AlertDialog.Builder(getActivity()); 
       ab.setMessage("Annuler la réservation ?").setPositiveButton("Oui", dialogClickListener) 
         .setNegativeButton("Non", dialogClickListener).show(); 
      } 
     }); 

     holder.displayDirections.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       launchMapActivity(); 
      } 
     }); 

     holder.callShop.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       launchDialer(); 
      } 
     }); 

     holder.discardOverlay.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View view) { 
       Animation hideOverlayAnimation = AnimationUtils.loadAnimation(getActivity(), R.anim.booking_overlay_dismiss); 
       holder.optionsOverlay.startAnimation(hideOverlayAnimation); 
       holder.optionsOverlay.setVisibility(View.GONE); 
       holder.optionsOverlay.clearAnimation(); 
      } 
     }); 
    } 


    private void sendCancelBookingToAPI(String id_booking) throws JsonProcessingException { 

      BeeWylApiClient.cancelBooking(id_booking, new AsyncHttpResponseHandler() { 

       @Override 
       public void onSuccess(int i, Header[] headers, byte[] bytes) { 
        try { 
         Log.v("xdebug CANCEL", new String(bytes, "UTF_8")); 
        } catch (UnsupportedEncodingException e) { 
         e.printStackTrace(); 
        } 
       } 
       @Override 
       public void onFailure(int i, Header[] headers, byte[] bytes, Throwable throwable) { 
        Log.v("xdebug CANCEL ERROR", String.valueOf(throwable)); 
       } 
      }); 
    } 

    DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { 
     @Override 
     public void onClick(DialogInterface dialog, int which) { 
      switch (which){ 
       case DialogInterface.BUTTON_POSITIVE: 
        Animation hideOverlayAnimation = AnimationUtils.loadAnimation(getActivity(), R.anim.booking_overlay_dismiss); 
        mBookingsListView.getChildAt(mSelectedItem-mBookingsListView.getFirstVisiblePosition()).startAnimation(hideOverlayAnimation); 
        new Handler().postDelayed(new Runnable() { 
         public void run() { 
          try { 
           sendCancelBookingToAPI(mBookings.get(mSelectedItem).getId()); 
          } catch (JsonProcessingException e) { 
           e.printStackTrace(); 
          } 
          mBookings.remove(mSelectedItem); 
          mSelectedItem = -1; 
          updateBookingsListView(mBookings); 
         } 
        }, hideOverlayAnimation.getDuration()); 
        break; 

       case DialogInterface.BUTTON_NEGATIVE: 
        dialog.cancel(); 
        break; 
      } 
     } 
    };  
} 

} 

并膨胀项:

<?xml version="1.0" encoding="utf-8"?> 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
      android:layout_width="match_parent" 
      android:layout_height="wrap_content" 
      android:paddingTop="5dp" 
      android:paddingLeft="5dp" 
      android:paddingRight="5dp" 
    > 


<LinearLayout 
     android:id="@+id/bookingItemLL" 
     android:layout_width="match_parent" 
     android:layout_height="151dp" 
     android:orientation="horizontal" 
     android:weightSum="100" 
     android:background="@drawable/product_item_rectangle" 
     > 

    <ImageView 
      android:id="@+id/bookedProductImage" 
      android:layout_width="150dp" 
      android:layout_height="150dp" 
      android:background="@android:color/white" 
      android:src="@drawable/nivea" 
      /> 


    <LinearLayout 
      android:layout_width="fill_parent" 
      android:layout_height="match_parent" 
      android:orientation="vertical" 
      android:gravity="center_vertical" 
      > 
     <TextView 
       android:id="@+id/bookingProductName" 
       android:layout_width="match_parent" 
       android:layout_height="wrap_content" 
       android:layout_marginLeft="10dp" 
       android:text="BRUME NIVEA" 
       android:textColor="@color/ProductsBlue" 
       android:textSize="16dp" 
       android:textStyle="bold" 
       /> 

     <TextView 
       android:id="@+id/bookedProductPrice" 
       android:layout_width="wrap_content" 
       android:layout_height="wrap_content" 
       android:text="Prix lors de la réservation : 24,90€" 
       android:textSize="12dp" 
       android:layout_marginLeft="10dp" 
       android:layout_marginTop="5dp" 
       android:textColor="@color/ProductsBlue"      android:layout_gravity="left" 
       /> 

     <TextView 
       android:id="@+id/bookingShopName" 
       android:layout_width="match_parent" 
       android:layout_height="wrap_content" 
       android:layout_marginLeft="10dp" 
       android:layout_marginTop="5dp" 
       android:text="Magasin" 
       android:textSize="12dp" 
       android:textColor="@color/ProductsBlue" 
       /> 

     <TextView 
       android:layout_width="match_parent" 
       android:layout_height="wrap_content" 
       android:layout_marginLeft="10dp" 
       android:layout_marginTop="5dp" 
       android:text="Réservé jusqu'au" 
       android:textSize="12dp" 
       android:textColor="@color/ProductsBlue"      /> 

     <TextView 
       android:id="@+id/bookingEndDate" 
       android:layout_width="match_parent" 
       android:layout_height="wrap_content" 
       android:layout_marginLeft="10dp" 
       android:text="-" 
       android:textSize="12dp" 
       android:textColor="@color/ProductsBlue"      /> 
    </LinearLayout> 
</LinearLayout> 


<RelativeLayout android:id="@+id/bookingOptionsOverlay" 
       android:layout_width="match_parent" 
       android:layout_height="150dp" 
       android:background="#EEFFFFFF" 
       android:visibility="gone"> 


    <ImageView 
      android:id="@+id/discardOverlay" 
      android:layout_width="30dp" 
      android:layout_height="30dp" 
      android:layout_alignParentRight="true" 
      android:layout_alignParentTop="true" 
      android:src="@drawable/ic_discard_booking_overlay" 
      android:padding="5dp" 
      /> 


    <Button android:id="@+id/callShop" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:text="APPELER" 
      android:layout_weight="1" 
      android:background="#00000000" 
      android:drawableTop="@drawable/booking_call" 
      android:textColor="@color/ProductsBlue" 
      android:textSize="14dp" 
      android:layout_alignParentLeft="true" 
      android:layout_centerVertical="true" 
      android:drawablePadding="20dp" 
      android:layout_marginLeft="20dp" 
      /> 
    <Button android:id="@+id/cancelBooking" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:text="ANNULER" 
      android:layout_weight="1" 
      android:background="#00000000" 
      android:drawableTop="@drawable/booking_cancel" 
      android:textColor="@color/ProductsBlue" 
      android:textSize="14dp" 
      android:layout_centerInParent="true" 
      android:drawablePadding="20dp" 

      /> 
    <Button android:id="@+id/routeShop" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:text="ITINERAIRE" 
      android:layout_weight="1" 
      android:background="#00000000" 
      android:drawableTop="@drawable/booking_route" 
      android:textColor="@color/ProductsBlue" 
      android:textSize="14dp" 
      android:layout_alignParentRight="true" 
      android:layout_centerVertical="true" 
      android:drawablePadding="20dp" 
      android:layout_marginRight="20dp" 
      /> 

    </RelativeLayout> 

</RelativeLayout> 
+0

ViewHolders使性能提升最小化,但会在编码时间和运行时间中引入许多问题,如果您绝对确定您的适配器速度较慢,请使用它们 – pskink 2014-10-07 09:37:58

+0

请记住,List将使用每个项目作为模板,如果您使用类似holder.optionsOverlay.setVisibility(View.GONE);您必须在getView()方法中添加一些逻辑,以确保您将可见性设置回VISIBLE,如果convertView的实例用于另一个项目 – 2red13 2014-10-07 09:50:01

+0

@ 2red13确实,但是optionsOverlay总是按预期显示。正如你在视频中看到的那样,当我点击隐形物品时,它仍会触发它。 – Stanislasdrg 2014-10-07 09:57:49

回答

2

你的问题来自于重新使用convertView。

当前一个项目点击了OnClickListener,并且在那里项目的可见性被设置为GONE。稍后,这个相同的视图被回收并作为convertView传递给getView()。因为您正在重新使用它,而不重置所做的任何更改,所以您现在正在使用View查看未处于已知状态的新项目。你应该确保你在使用convertView之前撤销任何更改。

快速修复是不重复使用传递给getView()的convertView。所以,在你的代码,你是否可以重新使用convertView:

if(convertView == null) 

破坏活动是检查只是为了看看事情开始工作:

if(true) 

如果做的伎俩,你大概会想要正确地修复它。

在上述检查的else子句中,您从标签中获取物品的持有者。同时撤消您的OnClickListeners可能做出的任何更改。你想从一个视图开始,看一个已知状态的新项目。你应该明确地初始化它。例如:“在convertView被重用覆盖视图,而不是我的项目的根视图”

if(convertView == null) { 
    // ... snipped all the initialization ... 
} else { 
    holder = (ViewHolder)convertView.getTag(); 
    convertView.setVisibility(View.VISIBLE); 
} 

更新

我从来没有使用一个“异质”适配器,所以我真的不能回答为什么Adapter.getView()的Android开发人员文档说明有关convertView参数:

旧的视图重新使用,如果可能的话。注意:在使用之前,您应该检查此视图是否为非空和适当的类型。如果无法将此视图转换为显示正确的数据,则此方法可以创建新视图。异构列表可以指定它们的视图类型数量,因此这个视图始终是正确的类型(请参阅getViewTypeCount()和getItemViewType(int))。

强调的位说,你不能依靠系统传递给你正确类型的convertView,而最后一句说的是相反的(正如我读的那样)。

基本上,我不知道它为什么不起作用。我想在测试中,你检查,如果你必须抬高一个新的视图自己

if(convertView == null) 

,你也应该检查它是否是正确的一种说法:

if(convertView == null || getItemViewTypeFromView(convertView) != type) 

哪里getItemViewTypeFromView()是这样的:

private int getItemViewTypeFromView(View view) { 
    switch (view.getId()) { 
     case R.id.item_layout_root: 
      return TYPE_ITEM; 
     case R.id.placeholder_layout_root: 
      return TYPE_PLACEHOLDER; 
     default: 
      throw new UnsupportedOperationException(); 
    } 
} 

在项目和占位符布局中,给根元素一个id,以便区分它们。所以像这样:

<?xml version="1.0" encoding="utf-8"?> 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
     android:id="@+id/item_layout_root" 
     android:layout_width="match_parent" 
     android:layout_height="wrap_content" 
     android:paddingTop="5dp" 
     android:paddingLeft="5dp" 
     android:paddingRight="5dp" > 

    ... snipped the elements that make up the body of the layout ... 
</RelativeLayout> 

我还没有尝试过上述,所以我希望它适用于你。

祝你好运!

+0

感谢罗布,你的解决方案部分工作。如果我设置,如果(true)一切按预期工作。但是,就是这样,如果我想按照您的建议设置convertViews可见性,则不会。我不明白的是,convertView如何重用覆盖视图,而不是我的项目的根视图。设置为Gone的唯一视图是覆盖图,而不是整个视图本身。 – Stanislasdrg 2014-10-07 12:01:12

+0

非常感谢您的完整更新。我会研究它并尽快发布更新。欢呼 – Stanislasdrg 2014-10-08 13:08:55

+0

超级彻底和完整的回应。谢谢! – Terry 2015-06-17 13:53:27