3

ListView に ViewHolder と convertView の両方を実装しました。私の listView は、予約のリストを含むカスタム アダプターによって取り込まれます。アイテムをクリックすると、非表示のレイアウトが右から左にスライドしてボタンが表示されます。閉じるボタンをクリックして、このオーバーレイ レイアウトを閉じると、再び非表示になります。このオーバーレイ レイアウトには、項目を削除できる削除ボタンがあります。ここまでは順調ですね。アイテムを消去すると、期待どおりにアイテムが消え、アダプターが再ロードされます。下のアイテムは、削除されたアイテムの位置を占めますが、非表示のままです。アイテムをクリックしてオーバーレイ ビューをトリガーできるので、ここにあることがわかります。そのため、オーバーレイ ビューは表示されますが、アイテムは表示されません。なぜこれが起こっているのか分かりません。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>
4

1 に答える 1

2

あなたの問題は、convertView の再利用に起因します。

前のアイテムがクリックされると、OnClickListener が起動し、そこでアイテムの可視性が GONE に設定されました。その後、この同じビューがリサイクルされ、convertView として getView() に渡されました。行った変更をリセットせずに再利用しているため、現在、既知の状態ではない新しいアイテムのビューを操作しています。convertView を使用する前に、必ず変更を元に戻す必要があります。

簡単な修正は、getView() に渡される convertView を再利用しないことです。したがって、convertView を再利用できるかどうかを確認するコードでは、次のようになります。

if(convertView == null)

物事が機能し始めるかどうかを確認するためだけにチェックするサボタージュ:

if(true)

それで問題が解決する場合は、おそらく適切に修正する必要があります。

上記のチェックの else 句では、タグからアイテム ホルダーを取得しています。また、OnClickListeners が行った可能性のある変更を元に戻します。既知の状態の新しいアイテムのビューから始めたいとします。明示的に初期化する必要があります。例えば:

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

アップデート

「異機種間」アダプターを使用したことがないため、「convertView がアイテムのルート ビューではなくオーバーレイ ビューを再利用している」理由について、実際には答えられません。Adapter.getView()の Android 開発者向けドキュメントには、convertView引数について次のように記載されています。

可能であれば、再利用する古いビュー。注: を使用する前に、このビューが 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>

私は上記を試していないので、うまくいくことを願っています。

幸運を!

于 2014-10-07T10:37:45.490 に答える