2014-10-07 193 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