【问题标题】:RecyclerView item onClickListener doesn't work on a first click, but works on a second oneRecyclerView 项目 onClickListener 在第一次点击时不起作用,但在第二次点击时起作用
【发布时间】:2018-05-14 15:33:05
【问题描述】:

适配器

public class MoviesAdapter extends 
RecyclerView.Adapter<MoviesAdapter.MoviesViewHolder> {
private static final String baseMovieUrl = "http://image.tmdb.org/t/p/w500";
private ArrayList<Movie> movies;
private Context context;

public MoviesAdapter(ArrayList<Movie> movies, Context context) {
    this.movies = movies;
    this.context = context;
}

@NonNull
@Override
public MoviesViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
    return new MoviesViewHolder(layoutInflater.inflate(R.layout.movie_square, parent, false));
}

@Override
public void onBindViewHolder(@NonNull MoviesViewHolder holder, int position) {
    holder.setData(position);

}


@Override
public int getItemCount() {
    return movies.size();
}


public class MoviesViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    private ConstraintLayout movieConstraint;
    private TextView movieName;
    private TextView movieRate;
    private ImageView moviePoster;

    public MoviesViewHolder(View itemView) {
        super(itemView);
        movieConstraint = itemView.findViewById(R.id.movie_holder_constraints);
        movieName = itemView.findViewById(R.id.movie_name_tv);
        movieRate = itemView.findViewById(R.id.movie_rating_tv);
        moviePoster = itemView.findViewById(R.id.movie_iv);
        movieConstraint.setOnClickListener(this);

    }

    public void setData(int position) {
        movieConstraint.setTag(position);
        movieName.setText(movies.get(position).getMovieName());
        movieRate.setText(movies.get(position).getMovieRating() + "");
        if (!Objects.equals(movies.get(position).getMovieUrl(), "") && movies.get(position).getMovieUrl() != null) {
            Glide.with(moviePoster).load
                    (baseMovieUrl + movies.get(position).getMovieUrl())
                    .into(moviePoster);
        } else Log.i("No url!", "No url!");
    }

    @Override
    public void onClick(View view) {
        int pos = (int) view.getTag();
        view.setEnabled(false);
        changeActivity(pos);
        view.setEnabled(true);    // TODO Find a way to prevent 2 acctivites to open simultaniously
    }


    private void changeActivity(int pos) {
        Movie movie = movies.get(pos);
        Intent intent = new Intent(context, MovieInfo.class);
        intent.putExtra("movie", movie);
        ActivityOptionsCompat options = ActivityOptionsCompat.
                makeSceneTransitionAnimation((Activity) context, moviePoster, "profile");
        context.startActivity(intent, options.toBundle());
    }
}
}

和xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/movie_holder_constraints"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3dp">

<android.support.v7.widget.CardView
    android:id="@+id/movie_cv"
    android:layout_width="185dp"
    android:layout_height="250dp"
    android:layout_marginBottom="8dp"
    android:layout_marginEnd="8dp"
    android:layout_marginStart="8dp"
    android:layout_marginTop="8dp"
    android:focusable="false"
    android:focusableInTouchMode="false"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent">

    <android.support.constraint.ConstraintLayout
        android:id="@+id/inside_movie_constraints"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:focusable="false"
        android:focusableInTouchMode="false"
        >

        <ImageView
            android:id="@+id/movie_iv"
            android:layout_width="185dp"
            android:layout_height="180dp"
            android:scaleType="fitXY"
            android:focusable="false"
            android:focusableInTouchMode="false"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:srcCompat="@mipmap/ic_launcher" />

        <TextView
            android:id="@+id/movie_rating_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="2dp"
            android:text="9.5"
            android:textColor="@color/colorAccent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/movie_name_tv"
            app:layout_constraintVertical_bias="1.0" />

        <TextView
            android:id="@+id/movie_name_tv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:text="Guardian's Of The Galaxy"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintHorizontal_weight="1"
            android:gravity="center"
            android:includeFontPadding="false"
            app:layout_constraintTop_toBottomOf="@+id/movie_iv" />
    </android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>

单击recyclerView中的项目时,它不会打开活动或调用OnClickListener,在再次单击该函数后调用该函数。 滚动也可以让我点击这些项目。

我听说将 foucsable 更改为 false 可能会解决此问题,但正如您在 xml 中看到的那样,它没有。

编辑适配器: 我将 onClick 更改为 onBindViewHolder,它解决了问题,尽管我觉得它不是正确的方法。 你说什么?

package com.example.galzaid.movies;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.constraint.ConstraintLayout;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;

import java.util.ArrayList;
import java.util.Objects;

public class MoviesAdapter extends 
RecyclerView.Adapter<MoviesAdapter.MoviesViewHolder> implements 
View.OnClickListener {
private static final String baseMovieUrl = "http://image.tmdb.org/t/p/w500";
private ArrayList<Movie> movies;
private Context context;

public MoviesAdapter(ArrayList<Movie> movies, Context context) {
    this.movies = movies;
    this.context = context;
}

@NonNull
@Override
public MoviesViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
    return new MoviesViewHolder(layoutInflater.inflate(R.layout.movie_square, parent, false));
}

@Override
public void onBindViewHolder(@NonNull MoviesViewHolder holder, int position) {
    holder.setData(position);
    holder.itemView.setOnClickListener(this);
    holder.getAdapterPosition();
}


@Override
public int getItemCount() {
    return movies.size();
}

@Override
public void onClick(View view) {
    ImageView moviePoster = view.findViewById(R.id.movie_iv);
    changeActivity((int) view.getTag(), moviePoster);
}

private void changeActivity(int pos, ImageView moviePoster) {
    Movie movie = movies.get(pos);
    Intent intent = new Intent(context, MovieInfo.class);
    intent.putExtra("movie", movie);
    ActivityOptionsCompat options = ActivityOptionsCompat.
            makeSceneTransitionAnimation((Activity) context, moviePoster, "profile");
    context.startActivity(intent, options.toBundle());
}


public class MoviesViewHolder extends RecyclerView.ViewHolder {
    private ConstraintLayout movieConstraint;
    private TextView movieName;
    private TextView movieRate;
    private ImageView moviePoster;

    public MoviesViewHolder(View itemView) {
        super(itemView);
        movieConstraint = itemView.findViewById(R.id.movie_holder_constraints);
        movieName = itemView.findViewById(R.id.movie_name_tv);
        movieRate = itemView.findViewById(R.id.movie_rating_tv);
        moviePoster = itemView.findViewById(R.id.movie_iv);

    }

    public void setData(int position) {
        movieConstraint.setTag(position);
        movieName.setText(movies.get(position).getMovieName());
        movieRate.setText(movies.get(position).getMovieRating() + "");
        if (!Objects.equals(movies.get(position).getMovieUrl(), "") && movies.get(position).getMovieUrl() != null) {
            Glide.with(moviePoster).load
                    (baseMovieUrl + movies.get(position).getMovieUrl())
                    .into(moviePoster);
        } else Log.i("No url!", "No url!");
    }
}
}

【问题讨论】:

标签: android android-recyclerview onclicklistener


【解决方案1】:

我相信你的问题出在这一行

movieConstraint.setOnClickListener(this);

你不应该把它设置成ConstraintLayout,把上面那行改成这个

itemView.setOnClickListener(this);

ViewHolder 构造函数中设置onClickListener 并没有错。

编辑:

为了获得onClick中的位置,您可以使用getAdapterPosition()方法。

【讨论】:

  • 试过了,没修好,但听起来像是程序化的方法
  • @Gal 尝试删除 setEnabled(false)
  • 不是这样,我找到了修复它的东西,但不确定这是错误发布的正确方法
  • 我只能从持有者调用 getAdapterPosition,不能从 onClick 方法调用。
  • @Gal 检查编辑后的答案,将itemView.setOnClickListener 放回构造函数,并让您的ViewHolder 在编辑中实现onClickListener。这样您就可以从onClickListener 呼叫getAdapterPosition。并将onClick 放回原来的位置。
【解决方案2】:

在绑定方法中设置 OnClickListener,在您的情况下为 public void setData(int position)

RecyclerView 顾名思义就是重用它创建的视图,所以对于每个项目,你应该从一开始就设置所有信息,包括 onClick 监听器

这应该可以解决您的问题,并且您将能够放弃使用 setTag 来区分视图

避免在 ViewHolder 构造函数中设置 onClick 侦听器。

【讨论】:

  • 我不认为这是真的,在ViewHolder 中设置onClickListener 并没有错,它更干净。为什么说应该避免。
  • 嗯,我发现它是最好的方法,因为当您的项目更复杂,并且您需要显示/隐藏某些内容或根据您的项目模型定义某些内容是否可点击时,您必须这样做它在每次绑定时使用,因为如果您不这样做,当您滚动时,某些项目视图将保持它们被绑定的方式,这是因为 RecyclerView 重用了已创建的视图
  • 这不是最好的方法,事实上,这是一个不好的方法。阅读this 答案以找出原因。简而言之,如果你把它放在onBindViewHolder 中,setOnClickListener 将为每个项目设置它实际上是不必要的。如果您不想在ViewHolder 中设置它(出于某种原因),最好在onCreateViewHolder 中设置它而不是onBindViewHolder
  • 你没看我说的吗?我说这对我来说是个好方法,正是因为如此。因为设置在每一个项目上。因此,当我的项目需要可点击时,因为它的模型是这样说的,我添加了侦听器,如果没有,我将之前存在的侦听器设为空,如果该项目被回收了。我看不出为什么在 onBind 中设置监听器与设置文本或不同图形等有任何不同。这也与当你给项目 selectableItemBackground 时你需要为 null 监听器的事实相对应,否则点击动画会发生什么时候不应该
  • 是的,但你说避免在 ViewHolder 构造函数中设置onClick,这是一个不好的建议。我链接的帖子实际上解释了为什么这是一种不好的方法,你读过吗?也不需要将它们回收的侦听器清空。如果您在ViewHolder 的构造函数中有onClickListener,您可以使用getAdapterPosition 来控制可以点击的视图。
猜你喜欢
  • 2013-09-15
  • 2016-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多