recyclerviewer中的itemView.getContext()强制应用程序在完成活动后关闭

时间:2018-10-02 18:28:57

标签: android

我目前正在处理我的项目,RecyclerViewer Java类文件中遇到了一些麻烦,每次我单击完整按钮时,都将代码((Activity)itemView.getContext()).finish();放入它时,它会强制关闭应用程序。怎么解决呢?当我单击完成按钮时,它将完成活动,然后重新加载活动。

这是我的代码:

package com.example.asus.hatidtubigandriversapp.adapter;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.example.asus.hatidtubigandriversapp.DeliveryListActivity;
import com.example.asus.hatidtubigandriversapp.R;
import com.example.asus.hatidtubigandriversapp.http.HttpHelper;
import com.example.asus.hatidtubigandriversapp.http.UrlList;

import java.util.ArrayList;
import java.util.List;

public class DeliveryListHelper extends RecyclerView.Adapter<DeliveryListHelper.ViewHolder> {
    private List<Integer> id = new ArrayList<>();
    private List<String> customer = new ArrayList<>();
    private List<String> qty = new ArrayList<>();
    private List<String> type = new ArrayList<>();
    private List<String> address = new ArrayList<>();

    public DeliveryListHelper(List<Integer> id, List<String> customer, List<String> qty, List<String> type, List<String> address) {
        this.id = id;
        this.customer = customer;
        this.qty = qty;
        this.type = type;
        this.address = address;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        View getView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_list_delivery, viewGroup, false);
        ViewHolder viewHolder = new ViewHolder(getView);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
        viewHolder.itemId = this.id.get(i);
        viewHolder.tCustomer.setText(this.customer.get(i));
        viewHolder.tOrderQty.setText(this.qty.get(i));
        viewHolder.tWaterType.setText(this.type.get(i));
        viewHolder.tAddress.setText(this.address.get(i));
    }

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

    public class ViewHolder extends RecyclerView.ViewHolder {
        int itemId = 0;
        TextView tCustomer, tOrderQty, tWaterType, tAddress;
        Button btnCompelete;
        public ViewHolder(@NonNull final View itemView) {
            super(itemView);
            tCustomer = (TextView) itemView.findViewById(R.id.tCustomer);
            tOrderQty = (TextView) itemView.findViewById(R.id.tOrderQty);
            tWaterType = (TextView) itemView.findViewById(R.id.tType);
            tAddress = (TextView) itemView.findViewById(R.id.tAddress);

            btnCompelete = (Button) itemView.findViewById(R.id.btnComplete);

            btnCompelete.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    String endpoint = UrlList.main_url + "type=completedeliver&id=" + String.valueOf(itemId);
                    new DeliveryListHelper.CompleteDeliver(itemView.getContext()).execute(endpoint);
                    itemView.getContext().startActivity(new Intent(itemView.getContext(), DeliveryListActivity.class));
                    ((Activity)itemView.getContext()).finish(); `// and here i put this finish`
                }
            });
        }
    }

    public class CompleteDeliver extends AsyncTask<String, Void, String> {
        ProgressDialog pd;
        private Context context;

        public CompleteDeliver(Context context) {
            this.context = context;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();

            pd = new ProgressDialog(this.context);
            pd.setMessage("Completing...");
            pd.setCancelable(false);
            pd.show();
        }

        @Override
        protected void onPostExecute(String result) {
            pd.dismiss();
            getResult(result);
        }

        @Override
        protected String doInBackground(String... url) {
            HttpHelper httpHelper = new HttpHelper(this.context);

            return httpHelper.httpConnect(url[0]);
        }

        private void getResult(String result) {
            Toast.makeText(this.context,
                    result,
                    Toast.LENGTH_SHORT).show();
        }
    }
}

2 个答案:

答案 0 :(得分:0)

正如评论所建议的那样,您想取出.finish()并想在适配器上调用.notifyDataSetChanged()

答案 1 :(得分:0)

这将很长,但是我建议您遵循更传统的方法,将Adapter与Activity(如果需要)和ViewHolder与Adapter分离。即使您从未在其他地方重复使用它们。

这是什么意思,而不是依靠ViewHolder来获取活动,而是为您的viewHolder提供回调。顺便说一下,itemView.getContext()不一定是一项活动; itemView是View,因此,它的上下文不一定需要是Activity,在假设它时要小心。

现在您执行此操作:

Activity->Adapter->ViewHolder->try to cast context to Activity and do things

我要做的是:

  • 活动创建适配器/回收器视图。
  • 活动必须在必须执行操作时将适配器中的侦听器设置为被回调。
  • 适配器存储上述侦听器,每次创建新的ViewHolder时,它都会向每个viewHolder传递 new 侦听器。
  • 然后,ViewHolder将View#onClickListener()分配给您要触发操作的按钮/视图/小部件。
  • 在ViewHolder设置的onClickListener中,您可以将适配器传递给您的监听器(可能带有单击视图的getAdapterPosition())称为侦听器。
  • 适配器将接收此回调,并通过提供的位置获取该项目,然后调用该活动设置的原始侦听器,现在该项目就在手边。
  • 该活动将收到此消息,并会随其需要执行任何操作。

为什么要选择更复杂的路由?

您应该尽量使事物解耦;您的解决方案是通过尝试做不属于自己的事情来与框架作斗争。

您已经亲身体验了为什么ViewHolder在运行后台操作上不好;它不是为它创建/设计的。 它真的不知道(也不应该)开始活动。

适配器...适配器就是这样,它“适应”以一种格式(您的列表)出现的数据,以供某人(RecyclerView)使用。适配器是中间对象,它会与RecyclerView对话并为其提供实际视图,并在数据源(列表)和所述视图之间(通过ViewHolders)绑定数据,同时有效地缓存/重用有关意见;它当然不知道活动,也不必。

另一方面,活动/片段在上下文艺术中很精通。他们本身就是一个人,或者有能力完美地获得一个。 (注意:正如您通过itemView.getContext()所知,视图持有者也可以获取上下文),但是此上下文仅应用于“查看事物”。即,获取资源(字符串,颜色,可绘制对象),获取主题值等。不要替换(或更糟糕的是,跳过组件之间的正确通信)。

这在实践中看起来有多复杂?

我将发布一个简单的示例,并在适用的情况下主要使用伪代码。

(我将在下面解释这些内容)

在您的适配器中,添加以下内容:

// This is the interface to pass to the adapter
interface OnItemClickedListener {
    void onCompleteDeliver(int itemId);
}

在您的ViewHolder中,添加以下内容:

// This is the interface to pass to the ViewHolders. (put this in your ViewHolder, it makes it cleaner).
interface OnClickListener {
     void onCompleteClicked(int position);
}

让我们开始修改您的活动

您需要为视图充气,然后创建适配器并将其设置为RecyclerView。这是活动的工作,而您已经在做。

但是现在您已经定义了两个接口,请修改您的活动:

recyclerView = findViewById(…);
Adapter adapter = new Adapter(…);

// THIS IS NEW
adapter.setItemClickListener(itemId -> { 
   String endpoint = UrlList.main_url + "type=completedeliver&id=" + String.valueOf(itemId);
   new DeliveryListHelper.CompleteDeliver(getContext()).execute(endpoint);
   // that’s it, when the AsyncTask is done, you can finish or whatever is that you wanted to do. You are in an activity anyway.
});
recyclerView.setAdapter(adapter);

到目前为止,很好,我们只添加了怪异的setItemClickListener ...

现在在您的适配器中,添加上述方法,如下所示:

void setCompleteListener(OnItemClickedListener listener) {
   this.listener = listener; // save it, you will need it!
}

作为避免创建N个此类匿名类的好方法,通常最好只创建一个。因此在适配器中添加一个字段:

private ViewHolder.OnClickListener onClickListener;

此字段的类型是上面在ViewHolder内部定义的接口类型。

然后在您的onCreateViewHolder(仍在适配器内!)中进行延迟创建:

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
    View getView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_list_delivery, viewGroup, false);
        // Lazy create one, if it’s null, create it, otherwise, use it.
        if (onClickListener == null) {
             // note: the getItem(position) method is part of `ListAdapter`, if you are not using that, then you need to either create a similar way or access your data directly… like myList.get(position) or anywhere you store the object that is bound to this view holder. DataAdapter is very convenient :)
            onClickListener = (position) -> listener. onCompleteClicked(getItem(position));
        }

    // Pass the internal click listener to the viewHolder
    return new ViewHolder(getView, onClickListener); // need to modify VH to accept this… see below…
}

现在,我们来修改 ViewHolder

向其中添加一个使用OnClickListener的构造函数:

        ViewHolder(View itemView, OnClickListener listener) {
            super(itemView);
            this.listener = listener; //keep it
         …
        }

现在,我将要做的,而不是创建超过9000个View.OnClickListeners…,只需一个并重复使用它即可:

(仍在ViewHolder内部)

        View.OnClickListener onViewClickedListener = new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                 listener.onCompleteClicked(getAdapterPosition());
            }
        };

所有这些操作都是在单击时进行的,它将使用适配器本身的位置调用侦听器(请记住这是一个ViewHolder)。

通过这种方式,可以在任何位置重用此侦听器……因此,例如在ViewHolder构造函数中……在this.listener=listener行下方

btnCompelete = (Button) itemView.findViewById(R.id.btnComplete);
btnCompelete.setOnClickListener(onViewClickedListener); 

...

此时,图片或多或少完整。

如果从ViewHolder向后退:

  1. 您将常规OnClickListener分配给按钮(或其他任何按钮)。
  2. 单击此视图后,您将调用OnItemClicked(在构造时提供给您),然后执行listener.onCompleteClicked(getAdapterPosition());
  3. 这会将事件转发到您的适配器传递到这些viewHolders的onClickListener上。这是同一实例,因为它通过参数接收受影响的(被点击的)排名(因此我们可以重复使用它!)。
  4. 适配器具有此onClickListener,因此,当ViewHolder在此处传递事件时…适配器所需要做的就是从其数据源中获取项目…(即up2you…getItem(pos)很方便ListAdapter<T>的方法,但是如果使用 raw RecyclerView.Adapter<T>,则通常会在适配器中保留要显示的项目列表……因此,请查询数据源/列表/无论如何,以获取对与单击的视图持有者绑定的项目的引用。
  5. 获得项目引用后,适配器剩下的就是将事件立即传递给Activity…,这可以随意进行。

我希望这篇漫长的(可能是错别字)文章能帮助您了解这种方法的优点。

是否还有更多代码? 是的。

调试起来更难吗? 可能,但不是真的。

这是最佳方式吗? 不太可能,但是它在多个项目中使用了这些方法(以及许多其他相似但不太相似的方法),却从未听到过任何不好的消息。

当然,您可能会想将其短路,而不是传递位置,而是传递商品的实际“ ID”,但是又为什么呢?适配器负责提供(并维护)每个列表“项目”与您的ViewHolder之间的关系,让他们完成工作。

另一方面,ViewHolder寿命短,可重复使用且笨拙;不要让它做无法控制的事情;想象您的情况,从ViewHolder触发一个异步任务需要1分钟。用户在那个时间滚动…ViewHolder被重用…地狱休息。

无论如何,祝您好运,并欢迎您使用Stack Overflow。