2014-10-07 2 views
2

ViewHolder와 convertView를 모두 내 listView에 구현했습니다. 내 listView는 예약 목록이있는 사용자 지정 어댑터로 채워집니다. 항목을 클릭하면 보이지 않는 레이아웃이 오른쪽에서 왼쪽으로 슬라이드하여 단추를 표시합니다. 다시 숨겨 지도록 닫기 버튼을 클릭하여이 오버레이 레이아웃을 닫을 수 있습니다. 이 오버레이 레이아웃에는 항목을 삭제할 수있는 삭제 버튼이 있습니다. 지금까지 너무 좋아. 항목을 지울 때 항목이 예상대로 사라지면 어댑터가 다시로드됩니다. 아래 항목은 삭제 된 항목의 위치를 ​​차지하지만 보이지 않습니다. 내가보기에 항목을 클릭하여 오버레이보기를 트리거 할 수 있기 때문에 여기 있다는 것을 알고 있습니다. 그래서 ovelaying보기는 보이지만 항목은 보이지 않습니다. 왜 이런 일이 일어나는지 모르겠습니다. 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

+0

List는 템플릿을 사용하는 경우 각 항목을 템플릿으로 사용합니다 holder.optionsOverlay.setVisibility (View.GONE)와 같은 것; convertView의 인스턴스가 다른 항목에 사용되는 경우 가시성을 다시 VISIBLE로 설정하려면 getView() Methode에 일부 논리를 추가해야합니다. – 2red13

+0

@ 2red13 예 그렇지만 optionsOverlay는 항상 예상대로 표시됩니다. 비디오에서 볼 수 있듯이 보이지 않는 항목을 클릭하면 여전히 보이지 않습니다. – Stanislasdrg

답변

2

귀하의 문제는 convertView를 다시 사용에서 비롯됩니다.

이전 항목에 클릭이 발생하면 OnClickListener가 실행되고 거기에서 항목에 대한 가시성이 GONE으로 설정되었습니다. 나중에이 동일한 뷰가 재활용되어 getView()에 convertView로 전달됩니다. 변경 한 내용을 다시 설정하지 않고 다시 사용하므로 알려진 상태가 아닌 새 항목에 대한보기로 작업하고 있습니다. convertView를 사용하기 전에 변경 사항을 실행 취소해야합니다.

빠른 수정은 getView()에 전달 된 convertView를 재사용하지 않는 것입니다.

if(true) 

그건 당신이 아마 것이다 트릭을 수행하는 경우 : 가지 작업을 시작 있는지 확인하기 위해

if(convertView == null) 

파괴 공작 단지 확인 : 당신이 convertView를 다시 사용할 수 있습니다 경우에 따라서, 코드에서 다음 사항을 확인할 경우 그것을 올바르게 고치고 싶다.

위 체크의 else 절에서 태그에서 아이템 홀더를 가져 오는 중입니다. 또한 OnClickListeners가 만들 수있는 변경 사항을 취소합니다. 알려진 상태의 새 항목에 대한보기로 시작하려고합니다. 명시 적으로 초기화해야합니다. 예를 들면 : ". convertView 대신 내 항목의 루트보기의 오버레이 뷰를 재사용한다"

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

업데이트

난 정말 왜 대답 할 수없는, 그래서 나는 '이질적인'어댑터를 사용한 적이

Adapter.getView()에 대한 Android 개발자 문서에서 인수에 대해 말합니다.

가능하면 재사용 할 수있는 이전보기. 참고 :을 사용하기 전에이 뷰가 null이 아닌 이고 적절한 유형인지 확인해야합니다.이 뷰를 변환하여 올바른 데이터를 표시 할 수없는 경우이 메서드는 새 뷰를 만들 수 있습니다. 이기종리스트는 뷰의 수를 지정할 수 있기 (위해) 때문에,이 뷰는 항상 올바른 형태 (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

감사합니다. Rob, 해결책이 부분적으로 작동합니다. 모든 것이 예상대로 작동한다면 (true) 설정합니다. 그러나 그게 전부입니다, 당신이 제안한대로 convertViews 가시성을 설정하고 싶다면, 그렇지 않습니다. 내가 얻지 못하는 것은 convertView가 내 항목의 루트보기 대신 오버레이보기를 재사용하는 이유입니다. Gone으로 설정된 유일한보기는 전체 View 자체가 아니라 오버레이입니다. – Stanislasdrg

+0

전체 업데이트를 해주셔서 감사합니다. 나는 그것을 조사하고 가능한 빨리 업데이트를 게시 할 것이다. 환호 – Stanislasdrg

+0

매우 철저하고 완벽한 답변. 고맙습니다! – Terry

관련 문제