RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 642217
Accepted
RareScrap
RareScrap
Asked:2020-03-21 13:45:16 +0000 UTC2020-03-21 13:45:16 +0000 UTC 2020-03-21 13:45:16 +0000 UTC

不从 MainActivity 调用 GetView 适配器方法

  • 772

下午好。我正在学习为 android 编写应用程序,在其中一本书中我找到了一本培训手册——创建天气预报应用程序。我遵循了手册,但最终注意到以下内容:我的应用程序处理获取 json 文件、解析它并创建具有所需值的列表元素的任务,但适配器不会以任何方式显示我的列表元素。我使用调试器检查了项目中的所有方法,并注意到适配器的 getView 方法从未被调用过。我将它与我应该拥有的应用程序进行了多次比较,但没有发现显着差异。

单击按钮后应用程序应如何运行:

在此处输入图像描述

在我的案例中它的表现如何:

在此处输入图像描述

代码:MainActivity.java:

package ru.rarescrap.educationweatherview;

// MainActivity.java
// Вывод 16-дневного прогноза погоды для заданного города

import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.inputmethod.InputMethodManager; // Для сокрытия клавиатуры по нажатию FAB
import android.widget.EditText;
import android.widget.ListView;

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

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.List;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    // Список объектов Weather, представляющих прогноз погоды
    private List<Weather> weatherList = new ArrayList<>();

    // ArrayAdapter связывает объекты Weather с элементами ListView
    private WeatherArrayAdapter weatherArrayAdapter;
    private ListView weatherListView; // Для вывода информации

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Сгенерированный код для заполнения макета и настройки Toolbar
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // ArrayAdapter для связывания weatherList с weatherListView
        weatherListView = (ListView) findViewById(R.id.weatherListView);
        weatherArrayAdapter = new WeatherArrayAdapter(this, weatherList);
        weatherListView.setAdapter(weatherArrayAdapter);

        // FAB скрывает клавиатуру и выдает запрос к веб-сервису
        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Получить текст из locationEditText и создать URL веб-сервисы
                EditText locationEditText = (EditText) findViewById(R.id.locationEditText);
                URL url = createURL(locationEditText.getText().toString());

                // Скрыть клавиатуру и запустить GetWeatherTask для получения
                // погодных данных от OpenWeatherMap.org в отдельном потоке
                if (url != null) {
                    dismissKeyboard(locationEditText);
                    GetWeatherTask getLocalWeatherTask = new GetWeatherTask();
                    getLocalWeatherTask.execute(url);
                }else {
                    Snackbar.make(findViewById(R.id.coordinatorLayout),
                            R.string.invalid_url, Snackbar.LENGTH_LONG).show();
                }
            }
        });
    }

    // Клавиатура закрывается при касании кнопки FAB
    private void dismissKeyboard(View view) {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); // Этот метод может возвращать объекты многих разных типов, поэтому возвращаемое значение необходимо преобразовать к нужному типу
        imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }

    /***
     * Создание URL веб-сервисы openweathermap.org для названия города
     * @param city - Город, для которого ищется погодный прогноз
     * @return URL
     */
    private URL createURL(String city) {
        String apiKey = getString(R.string.api_key);
        String baseUrl = getString(R.string.web_service_url);

        try {
            // Создание URL для заданного города и температурной шкалы (Фаренгейт)
            /* Параметр units может принимать значения imperial (для шкалы Фаренгейта),
            metric (для шкалы Цельсия) или standard (для шкалы Кельвина) — если параметр
            units не указан, по умолчанию используется значение standard. Параметр cnt
            определяет количество дней в прогнозе. Максимальное значение равно 16,
            значение по умолчанию равно 7 (при некорректном количестве дней возвращается
            прогноз на 7 дней). По умолчанию прогноз возвращается в формате JSON, хотя
            вы можете добавить параметр mode со значением XML или HTML, чтобы получить
            данные в формате XML или веб-страницы соответственно.*/
            String urlString = baseUrl + URLEncoder.encode(city, "UTF-8") + "&units=imperial&cnt=16&APPID=" + apiKey;
            return new URL(urlString);
        }
        catch (Exception e) {
            e.printStackTrace();
        }

        return null; // Некорректный URL
    }

    /* Обращение к REST-совместимому веб-сервису за погодными данными
    и сохранение данных в локальном файле HTML */
    private class GetWeatherTask
    extends AsyncTask<URL, Void, JSONObject> {
        @Override
        protected JSONObject doInBackground(URL... params) {
            HttpURLConnection connection = null;

            try {
                connection = (HttpURLConnection) params[0].openConnection(); // Для выдачи запроса достаточно открыть объект подключения
                int response = connection.getResponseCode(); // Получить код ответа от веб-сервера

                if (response == HttpURLConnection.HTTP_OK) {
                    StringBuilder builder = new StringBuilder();

                    try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
                        String line;
                        while ((line = reader.readLine()) != null) {
                            builder.append(line);
                        }
                    }
                    catch (IOException e) {
                        Snackbar.make(findViewById(R.id.coordinatorLayout),
                                R.string.read_error, Snackbar.LENGTH_LONG).show();
                        e.printStackTrace();
                    }

                    connection.disconnect();
                    return new JSONObject(builder.toString());
                }else {
                    Snackbar.make(findViewById(R.id.coordinatorLayout),
                            R.string.connect_error, Snackbar.LENGTH_LONG).show();
                }
            }
            catch (Exception e) {
                Snackbar.make(findViewById(R.id.coordinatorLayout),
                        R.string.connect_error, Snackbar.LENGTH_LONG).show();
                e.printStackTrace();
            }
            finally {
                connection.disconnect(); // Закрыть HttpURLConnection
            }

            return null;
        }

        // Обработка ответа JSON и обновление ListView
        @Override
        protected void onPostExecute(JSONObject weather) {
            if (weather != null) {
                convertJSONtoArrayList(weather); // Заполнение weatherList
                weatherArrayAdapter.notifyDataSetChanged(); // Связать с ListView
                weatherListView.smoothScrollToPosition(0); // Прокрутить до верха
            }
        }
    }

    // Создание объектов Weather на базе JSONObject с прогнозом
    private void convertJSONtoArrayList(JSONObject forecast) {
        weatherList.clear(); // Стирание старых погодных данных

        try {
            // Получение свойства "list" JSONArray
            JSONArray list = forecast.getJSONArray("list");

            // Преобразовать каждый элемент списка в объект Weather
            for (int i = 0; i < list.length(); ++i) {
                JSONObject day = list.getJSONObject(i); // Данные за день
                // Получить JSONObject с температурами дня ("temp")
                JSONObject temperatures = day.getJSONObject("temp");

                // Получить JSONObject c описанием и значком ("weather")
                JSONObject weather = day.getJSONArray("weather").getJSONObject(0);

                // Добавить новый объект Weather в weatherList
                weatherList.add(new Weather(
                        day.getLong("dt"), // Временная метка даты/времени
                        temperatures.getDouble("min"), // Мин. температура
                        temperatures.getDouble("max"), // Макс. температура
                        day.getDouble("humidity"), // Процент влажности
                        weather.getString("description"), // Погодные условия
                        weather.getString("icon"))); // Имя значка
            }
        }
        catch (JSONException e) {
            e.printStackTrace();
        }
    }
}

WeatherArrayAdapter.java:

// Объект ArrayAdapter для отображения элементов List<Weather> в ListView
package ru.rarescrap.educationweatherview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter; // Родительский класс
import android.widget.ImageView;
import android.widget.TextView;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class WeatherArrayAdapter extends ArrayAdapter<Weather> {
    // Класс для повторного использования представлений списка при прокрутке
    private static class ViewHolder {
        ImageView conditionImageView;
        TextView dayTextView;
        TextView lowTextView;
        TextView hiTextView;
        TextView humidityTextView;
    }

    // Кэш для уже загруженных объектов Bitmap
    private Map<String, Bitmap> bitmaps = new HashMap<>();

    // Конструктор для инициализации унаследованных членов суперкласса
    public WeatherArrayAdapter(Context context, List<Weather> forecast) {
        /*
        в первом и третьем аргументах передаются объект Context (то есть активность,
        в которой отображается ListView) и List<Weather> (список выводимых данных).
        Второй аргумент конструктора суперкласса представляет идентификатор ресурса
        макета, содержащего компонент TextView, в котором отображаются данные ListView.
        Аргумент –1 означает, что в приложении используется пользовательский макет,
        чтобы элемент списка не ограничивался одним компонентом TextView.
         */
        super(context, -1, forecast);
    }

    // Создание пользовательских представлений для элементов ListView
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // Получение объекта Weather для заданной позиции ListView
        Weather day = getItem(position);

        //Объект, содержащий ссылки на представления элемента списка
        ViewHolder viewHolder;

        // Проверить возможность повторного использования ViewHolder для элемента, вышедшего за границы экрана
        if (convertView == null) { // Объекта ViewHolder нет, создать его
            viewHolder = new ViewHolder();
            LayoutInflater inflater = LayoutInflater.from(getContext());
            convertView = inflater.inflate(R.layout.list_item, parent, false); // последнем аргументе передается флаг автоматического присоединения представлений
            viewHolder.conditionImageView = (ImageView) convertView.findViewById(R.id.conditionImageView);
            viewHolder.dayTextView = (TextView) convertView.findViewById(R.id.dayTextView);
            viewHolder.lowTextView = (TextView) convertView.findViewById(R.id.lowTextView);
            viewHolder.hiTextView = (TextView) convertView.findViewById(R.id.hiTextView);
            viewHolder.humidityTextView = (TextView) convertView.findViewById(R.id.humidityTextView);
            convertView.setTag(viewHolder);
        }else { // Cуществующий объект ViewHolder используется заново
            viewHolder = (ViewHolder) convertView.getTag();
        }

        // Если значок погодных условий уже загружен, использовать его;
        // в противном случае загрузить в отдельном потоке
        if (bitmaps.containsKey(day.iconURL)) {
            viewHolder.conditionImageView.setImageBitmap(
            bitmaps.get(day.iconURL));
        }else {
            // Загрузить и вывести значок погодных условий
            new LoadImageTask(viewHolder.conditionImageView).execute(day.iconURL);
        }

        // Получить данные из объекта Weather и заполнить представления
        Context context = getContext(); // Для загрузки строковых ресурсов
        // Назначается текст компонентов TextView элемента ListView
        viewHolder.dayTextView.setText(context.getString(R.string.day_description, day.dayOfWeek, day.description)); // Первый аргумент - строка; Второй - аргументы для форматирования
        viewHolder.lowTextView.setText(context.getString(R.string.low_temp, day.minTemp));
        viewHolder.hiTextView.setText(context.getString(R.string.high_temp, day.maxTemp));
        viewHolder.humidityTextView.setText(context.getString(R.string.humidity, day.humidity));

        return convertView; // Вернуть готовое представление элемента
    }

    // TODO: Как полученное изображение присваивается viewHolder и представлению?
    // Кажись, изменение imageView так же изменяет и аргумент, переданный в конструкторе LoadImageTask(). Таким образом, создается нечно вроде "ссылки"
    // AsyncTask для загрузки изображения в отдельном потоке
    private class LoadImageTask extends AsyncTask<String, Void, Bitmap> {
        private ImageView imageView; // Для вывода миниатюры

        // Сохранение ImageView для загруженного объекта Bitmap
        public LoadImageTask(ImageView imageView) {
            this.imageView = imageView;
        }

        // загрузить изображение; params[0] содержит URL-адрес изображения
        @Override
        protected Bitmap doInBackground(String... params) {
            Bitmap bitmap = null;
            HttpURLConnection connection = null;

            try {
                URL url = new URL(params[0]); // Создать URL для изображения

                // Открыть объект HttpURLConnection, получить InputStream
                // и загрузить изображение
                connection = (HttpURLConnection) url.openConnection(); // Преобразование типа необходимо, потому что метод возвращает URLConnection

                try (InputStream inputStream = connection.getInputStream()) {
                    bitmap = BitmapFactory.decodeStream(inputStream);
                    bitmaps.put(params[0], bitmap); // Кэширование
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            }
            catch (Exception e) {
                e.printStackTrace();
            }
            finally { // Этот участок кода будет выполняться независимо от того, какие исключения были возбуждены и перехвачены
                connection.disconnect(); // Закрыть HttpURLConnection
            }

            return bitmap;
        }

        // Связать значок погодных условий с элементом списка
        // Выполняется в потоке GUI вроде как для вывода изображения
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            imageView.setImageBitmap(bitmap);
        }
    }
}

天气.java:

package ru.rarescrap.educationweatherview;

// Используются для преобразования временной метки каждого дня в название дня недели
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;

class Weather {
    // объекты String в Java неизменяемы (immutable), поэтому несмотря на такое объявление, их значения измениться не могут
    public final String dayOfWeek;
    public final String minTemp;
    public final String maxTemp;
    public final String humidity;
    public final String description;
    public final String iconURL;

    // Конструктор
    public Weather(long timeStamp, double minTemp, double maxTemp, double humidity, String description, String iconName) {
        // NumberFormat для форматирования температур в целое число
        NumberFormat numberFormat = NumberFormat.getInstance();
        numberFormat.setMaximumFractionDigits(0); // Запрещает числа почле запятой

        this.dayOfWeek = convertTimeStampToDay(timeStamp); // Получение названия дня недели и инициализации dayOfWeek
        this.minTemp = numberFormat.format(minTemp) + "\u00B0F"; // Минимальная температура по Фаренгейту
        this.maxTemp = numberFormat.format(maxTemp) + "\u00B0F"; // Максимальная температура по Фаренгейту
        this.humidity = NumberFormat.getPercentInstance().format(humidity / 100.0);
        this.description = description; // Инициализирует описание погодных условий
        this.iconURL = "http://openweathermap.org/img/w/" + iconName + ".png"; // Изображение погодных условий для погоды
    }

    // Преобразование временной метки в название дня недели (Monday, ...)
    private static String convertTimeStampToDay(long timeStamp) {
        Calendar calendar = Calendar.getInstance(); // Объект Calendar
        calendar.setTimeInMillis(timeStamp * 1000); // Получение времени
        TimeZone tz = TimeZone.getDefault(); // Часовой пояс устройства

        // Поправка на часовой пояс устройства
        calendar.add(Calendar.MILLISECOND, tz.getOffset( calendar.getTimeInMillis() ));

        // Объект SimpleDateFormat, возвращающий название дня недели
        SimpleDateFormat dateFormatter = new SimpleDateFormat("EEEE"); // EEEE - первый четыре буквы для недели
        return dateFormatter.format(calendar.getTime());
    }
}

Если вам не удобно читать код тут, я выложил его на гитхаб для вас: https://github.com/RareScrap/EducationWeatherView/tree/for_stackOverFlw

Всю голову уже сломал над этой проблемой. Заранее спасибо!

UPDATE 1: Если верить этому треду, то проблем причин у нас может быть несколько

  1. Адаптер теряет ссылку на список. Я прошелся отладчиком по всей проге и этот вариант отпадает. На скрине ниже показывается одинаковый номер в объекте листа и листа адаптера (насколько я могу судить, это означает что ссылка одна и та же) 在此处输入图像描述

  2. В адаптер постоянно добавляется новый список. Понятие не имею как это мешает моей задаче. буду благодарен за объяснения.

  3. Данные добавляются в адаптер до их фактического добавления в лист. Инфа 100, что такого нет. Потому что я просто не могу увидеть это :Р

UPDATE 2 Предпринял радикальную меру: взял код из мануала и просто копипастнул в моей проект - не заработало. Очевидно, что ошибка не в коде, т.к. пример из мануала работает как нужно. У проблемы ноги растут из Gradle'а! Готов поспорить что при переходе на новую версию градла некоторые вещи перестали работать. Завтра попробую покопаться откатить градл-скрипт и посмотрим что из этого выйдет. Это не первый раз, когда из-за апдейта градла у меня вылезают непредсказуемые ошибки. Где хоть найти чейжлог с изменениями при каждом обновлении?

UPDATE 3: С откаченным градлом все работает. Вот только пространство имен app в xml разметках более не работает.

UPDATE 4: Проблема была в разметке.

java
  • 2 2 个回答
  • 10 Views

2 个回答

  • Voted
  1. Andrew Grow
    2020-03-21T13:51:40Z2020-03-21T13:51:40Z

    Проблема в том, что когда вы получили данные, распарсили их и записали в weatherList, вы не создаёте заново адаптер. Он так и остаётся у вас пустым - созданным в самом начале ещё без данных. Вы думали, что адаптер обновит информацию путём вызова метода

    weatherArrayAdapter.notifyDataSetChanged(); // Связать с ListView
    

    но на самом деле, этот метод notifyDataSetChanged только обновляет информацию для имеющихся items, а у вас их ещё нет (список-то пустой был, когда создавался). вместо этой строки

    weatherArrayAdapter.notifyDataSetChanged();
    

    сделайте

    weatherArrayAdapter = new WeatherArrayAdapter(this, weatherList);
    weatherListView.setAdapter(weatherArrayAdapter);
    

    Кстати, из метода onCreate создание адаптера этими строками можно убрать, никакой пользы они там не несут.

    Удачи )

    • 4
  2. Best Answer
    RareScrap
    2020-03-23T11:19:30Z2020-03-23T11:19:30Z

    Причина проблемы не в коде и не в Gradle-скриптах, а в разметке!

    列表没有显示是因为text field的android:layout_height设置为much_parent。因此,文本字段完全占据了整个屏幕空间,将列表的高度压缩为零。因为 不可能显示具有这样高度的列表中的元素,这就是为什么不调用适配器的 getView() 的原因。通过将文本框的 android:layout_height 固定为 wrap_content 我终于在列表中看到了我的项目

    !!!一定要检查你的标记文件,不要重复我的错误!!!

    更正的标记代码:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 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/content_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:orientation="vertical"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        tools:context="ru.rarescrap.educationweatherview.MainActivity"
        tools:showIn="@layout/activity_main">
    
        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <EditText
                android:id="@+id/locationEditText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/hint_text"
                android:maxLines="1" />
        </android.support.design.widget.TextInputLayout>
    
            <ListView
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:id="@+id/weatherListView" />
    </LinearLayout>
    
    • 1

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5