1

我有 3 个课程,我希望使用自动完成文本框向用户显示来自 Web 服务(rest api)的某些数据(又名城市)。我已经在我自己的应用程序的各种功能上使用了这个实现,但由于某种原因,textchangedlistener 中存在同步问题......

CitiesArrayAdapter.java(显示不同的视图,在我的例子中是“城市,州”):

package com.android.lzgo.adapters;

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

import com.android.lzgo.activities.LiftSearchActivity;
import com.android.lzgo.activities.R;
import com.android.lzgo.models.City;
import com.android.lzgo.models.Lift;

import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class CitiesArrayAdapter extends ArrayAdapter<City> {

    private static final String TAG = CitiesArrayAdapter.class.getName();

    private final ArrayList<City> cities;
    private int viewResourceId;

    public CitiesArrayAdapter(Context context, int textViewResourceId, ArrayList<City> results) {
        super(context, textViewResourceId, results);
        this.cities = results;
        this.viewResourceId = textViewResourceId;
    }


    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // assign the view we are converting to a local variable
        View v = convertView;

        if (v == null) {
            LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            v = inflater.inflate(viewResourceId, null);
        }

        City i = cities.get(position);

        Log.d(TAG, "Here is my value: " + i);

        if (i != null) {

            TextView tt = (TextView) v.findViewById(android.R.id.text1);

            Log.d(TAG, "Name: " + i.getName() + ", " + i.getProvince_name());

            if (tt != null){
                tt.setText("Name: " + i.getName() + ", " + i.getProvince_name());
            }
        }

        // the view must be returned to our activity
        return v;

    }

}

CitiesResponderFragment.java (这是我从我的 rest api 获取值的方式):

package com.android.lzgo.fragment;

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

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import com.android.lzgo.activities.LiftSearchActivity;
import com.android.lzgo.definitions.Constants;
import com.android.lzgo.models.City;
import com.android.lzgo.service.LzgoService;
import com.google.gson.Gson;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.Toast;

public class CitiesResponderFragment extends LzgoResponderFragment {
    private static String TAG = CitiesResponderFragment.class.getName();

    private List<City> mCities;
    ArrayAdapter<City> adapter;
    private String enteredCharacters;
    LiftSearchActivity activity;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        activity = (LiftSearchActivity) getActivity();

        // This gets called each time our Activity has finished creating itself.
        getCities();

    }

    private void getCities() {

        if (mCities == null && activity != null) {
            Intent intent = new Intent(activity, LzgoService.class);
            intent.setData(Uri.parse(Constants.REST_CITIES_AUTOCOMPLETE));

            Bundle params = new Bundle();
            params.putString("search", getenteredCharacters());

            intent.putExtra(LzgoService.EXTRA_HTTP_VERB, LzgoService.GET);
            intent.putExtra(LzgoService.EXTRA_PARAMS, params);
            intent.putExtra(LzgoService.EXTRA_RESULT_RECEIVER, getResultReceiver());

            // Here we send our Intent to our RESTService.
            activity.startService(intent);
        }                
    }

    @Override
    public void onRESTResult(int code, String result) {
        Log.e(TAG, Integer.toString(code));
        Log.e(TAG, result);

        // Check to see if we got an HTTP 200 code and have some data.
        if (code == 200 && result != null) {
            mCities = getCitiessFromJson(result);

            adapter = activity.getArrayAdapter();

            adapter.clear();

            for( City city : mCities){
                //debugging
                Log.d(TAG, "City : " + city.getName());
                adapter.add(city);
                adapter.notifyDataSetChanged();
            }

            getCities();               

        }
        else {
            Activity activity = getActivity();
            if (activity != null && code == 400) {
                Toast.makeText(activity, result, Toast.LENGTH_SHORT).show();
            }
            else
                Toast.makeText(activity, "Failed to load lzgo data. Check your internet settings.", Toast.LENGTH_SHORT).show();
        }
    }

    private List<City> getCitiessFromJson(String json) {
        ArrayList<City> cityList = new ArrayList<City>();
        Gson gson = new Gson();

        try {
            JSONObject citiesWrapper = (JSONObject) new JSONTokener(json).nextValue();
            JSONArray  cities        = citiesWrapper.getJSONArray("cities");

            for (int i = 0; i < cities.length(); i++) {
                //JSONObject city = cities.getJSONObject(i);
                String jsonCity = cities.getString(i);
                City city = gson.fromJson( jsonCity, City.class );

                //Log.e(TAG, "Hurray! Parsed json:" + city.getString("name"));
                //cityList.add(city.getString("name"));
                cityList.add(city);
            }
        }
        catch (JSONException e) {
            Log.e(TAG, "Failed to parse JSON.", e);
        }

        return cityList;
    }

    public String getenteredCharacters() {
        return enteredCharacters;
    }

    public void setenteredCharacters(String characters) {
        this.enteredCharacters = characters;
    }


}

LiftSearchActivity.java(我的 FragmentActivity):

package com.android.lzgo.activities;

import java.util.ArrayList;

import com.android.lzgo.adapters.CitiesArrayAdapter;
import com.android.lzgo.fragment.CitiesResponderFragment;
import com.android.lzgo.models.City;

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.widget.AutoCompleteTextView;
import android.widget.DatePicker;

public class LiftSearchActivity extends FragmentActivity{

    private static final String TAG = LiftSearchActivity.class.getName();

    // User lift input
    private AutoCompleteTextView autoCityFrom;
    private AutoCompleteTextView autoCityTo;
    private DatePicker date;

    private CitiesArrayAdapter adapter;
    private ArrayList<City> mCities ;

    int year , month , day;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.lift_search);

        mCities = new ArrayList<City>();

        adapter = new CitiesArrayAdapter(this,
                android.R.layout.simple_dropdown_item_1line, mCities);

        autoCityFrom = (AutoCompleteTextView) findViewById(R.id.cityFrom);
        autoCityTo = (AutoCompleteTextView) findViewById(R.id.cityTo);

        adapter.setNotifyOnChange(true);

        autoCityFrom.setAdapter(adapter);
        autoCityTo.setAdapter(adapter);

        autoCityFrom.addTextChangedListener(new TextWatcher() {
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                // no need to do anything
            }

            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                if (((AutoCompleteTextView) autoCityFrom).isPerformingCompletion()) {
                    return;
                }
                if (charSequence.length() < 2) {
                    return;
                }      

                String query = charSequence.toString();

                getCities(query);

            }

            public void afterTextChanged(Editable editable) {
            }
        });

        autoCityTo.addTextChangedListener(new TextWatcher() {
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                // no need to do anything
            }

            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                if (((AutoCompleteTextView) autoCityTo).isPerformingCompletion()) {
                    return;
                }
                if (charSequence.length() < 2) {
                    return;
                }

                String query = charSequence.toString();

                getCities(query);
            }

            public void afterTextChanged(Editable editable) { 
            }
        });

        date = (DatePicker) findViewById(R.id.dpResult);
    }

    public void searchLifts(View view) {

        Intent intent = new Intent(this, LiftsResultActivity.class);

        //While autocomplete doesn't work hardcore value...
        intent.putExtra("from", Integer.toString(9357)); // Sherbrooke
        intent.putExtra("to", Integer.toString(6193)); // Montreal
        intent.putExtra("date", Integer.toString(date.getMonth()+1) + "-" + Integer.toString(date.getDayOfMonth()) + "-" + Integer.toString(date.getYear()));
        startActivity(intent);
    }

    public void getCities(String query) {
        FragmentManager     fm = getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();

        CitiesResponderFragment responder = (CitiesResponderFragment) fm.findFragmentByTag("RESTResponder");

        responder = new CitiesResponderFragment();
        responder.setenteredCharacters(query);
        ft.add(responder, "RESTResponder");

        ft.commit();
    }

    public CitiesArrayAdapter getArrayAdapter() {
        // TODO Auto-generated method stub
        return adapter;
    }

}

我得到了正确的结果。但是我的服务似乎没有在我的活动中填充我的数组适配器,当我尝试显示我的第一个“城市”时,我的适配器不包含任何内容。我想知道我是否必须放置一个 notifydatasetchanged (我试过了,但没有用)。我有点困惑......任何指针?

在调试应用程序时,我注意到 ArrayAdapter 的属性 mObjects 被清除,即使关联的 ArrayList 有元素,然后属性 mOriginalValues 被第一次加载的字符串填充。

4

2 回答 2

1

在没有看到完整的代码库(+ 数据)的情况下,我不知道是否有人可以指出您的代码失败的原因。但我认为问题更多来自您设置整个自动完成相关代码的方式,而不是一些明显的行错误。下面我将尝试解决其中的一些问题(从我的角度来看):

首先,在TextWatcherfromLiftSearchActivity你调用getCities()添加的方法中,每次用户修改自动完成文本时,aFragmentActivity. 我真的怀疑你想要这个,你可能应该看看只有一个Fragment可以Activity调用刷新(或更新)方法,并将新的过滤器文本传递给它。如果用户旋转手机,这些片段也会因为配置更改而重新创建。

其次,在CitiesResponderFragment类中调用片段的getCities()方法(如果您没有任何数据),在onActivityCreated该方法中启动更新服务(?!)。现在与第一点相关,您最终可能会进行很多不必要的查询,例如,如果用户输入 4 个字符,然后决定删除其中一个字符,因为它不正确,您最终会添加 5 个片段,来自其中 3 将使服务启动/查询数据。

最后,我不确定您是否了解AutoCompleteTextView引擎盖下的工作原理。为了向下拉菜单提供建议,AutoCompleteTextView小部件将过滤其适配器(adapter.getFilter())并将匹配过滤器的项目显示为建议。我不知道您是否在 上设置了一些阈值,AutoCompleteTextView但最初输入的前 3 个字符的自动完成将为空,因为您在首次设置Activity. 前两个字符不会显示任何内容,因为您从一个空列表开始并且您没有添加任何新片段(charSequence.length() < 2)。第三个字符很可能也不会显示任何内容,因为创建片段、启动服务和获取数据的开销几乎肯定会大于适配器过滤的工作(仍然会看到最初的空列表) . 我不知道您是否测试过,但是从这第四个字符开始,适配器中应该有一些元素,并且过滤应该显示一些东西。清除适配器并在其中添加新数据只会使该数据可用于在AutoCompleteTextView.

进行过滤的正确方法是进一步扩展您的适配器并实现该getFilter()方法以返回您自己的Filter实现,该实现将查询数据存储以获取新的过滤项。过滤方法在后台线程上运行,我认为您可以Service通过 REST 回调实现您当前的逻辑。

于 2012-11-30T13:01:17.503 回答
0

请参阅 REST API 自动完成示例:https ://subversion.assembla.com/svn/rockitsearch-android/

虽然它是我的服务的一部分,但它也可以作为一个示例,说明如何将自己的 REST API 集成到 AutoCompleteTextView 控件中。如果您对自动完成第 3 方服务感兴趣,请访问:www.rockitsearch.com

于 2012-11-25T02:27:02.470 回答