Android 天气APP(十五)增加城市搜索、历史搜索记录

//EventBus

api ‘org.greenrobot:eventbus:3.1.1’

改动build.gradle记得Sync一下

其实这个EventBus和广播差不多,不过也有区别,它的用法是,在哪个页面接收消息,就在哪个页面绑定和解绑消息。

下面来运用一下,首先是在项目包下创建一个eventbus包,包下创建一个SearchCityEvent的消息类

在这里插入图片描述

代码如下:

package com.llw.goodweather.eventbus;

/**

  • 搜索城市消息事件

*/

public class SearchCityEvent {

public final String mLocation;

public final String mCity;

public SearchCityEvent(String location,String city) {

this.mLocation = location;

this.mCity = city;

}

}

然后就是使用了,发送消息方,我们在SearchCityActivity中的item点击的时候发送消息

在这里插入图片描述

//发送消息

EventBus.getDefault().post(new SearchCityEvent(mList.get(position).getLocation(),

mList.get(position).getParent_city()));

然后是接收消息方

在这里插入图片描述

在这里插入图片描述

OK,现在运行一下看看效果吧!

在这里插入图片描述

然后我们在这里面放入接口请求

在这里插入图片描述

再运行一次

在这里插入图片描述

很好,基本功能已经实现了,接下来就是关于这个历史搜索记录的实现了。然后再修改MainActivity中点击跳转到搜索城市页面的代码

SPUtils.putBoolean(Constant.FLAG_OTHER_RETURN, false, context);//缓存标识

⑥ 增加历史搜索记录


首先使我们点击输入框的时候出现上一次输入的文字,可以设置一个默认的值,比如深圳有两个方法,一个是初始化数据,另一个是保存输入到的数据,

首先创建item的布局

item_tv_history.xml

<?xml version="1.0" encoding="utf-8"?>

<TextView xmlns:android=“http://schemas.android.com/apk/res/android”

android:gravity=“center_vertical”

android:paddingLeft=“@dimen/dp_16”

android:layout_width=“match_parent”

android:singleLine=“true”

android:ellipsize=“marquee”

android:background=“@color/white”

android:textSize=“@dimen/sp_14”

android:textColor=“@color/black”

android:layout_height=“@dimen/dp_40”>

也比较简单,就一个TextView

然后就会业务代码了,代码如下

/**

  • 使 AutoCompleteTextView在一开始获得焦点时自动提示

  • @param field 保存在sharedPreference中的字段名

  • @param autoCompleteTextView 要操作的AutoCompleteTextView

*/

private void initAutoComplete(String field, AutoCompleteTextView autoCompleteTextView) {

SharedPreferences sp = getSharedPreferences(“sp_history”, 0);

String etHistory = sp.getString(“history”, “深圳”);//获取缓存

String[] histories = etHistory.split(“,”);//通过,号分割成String数组

ArrayAdapter adapter = new ArrayAdapter(this, R.layout.item_tv_history, histories);

// 只保留最近的50条的记录

if (histories.length > 50) {

String[] newHistories = new String[50];

System.arraycopy(histories, 0, newHistories, 0, 50);

adapter = new ArrayAdapter(this, R.layout.item_tv_history, newHistories);

}

//AutoCompleteTextView可以直接设置数据适配器,并且在获得焦点的时候弹出,

//通常是在用户第一次进入页面的时候,点击输入框输入的时候出现,如果每次都出现

//是会应用用户体验的,这里不推荐这么做

autoCompleteTextView.setAdapter(adapter);

autoCompleteTextView.setOnFocusChangeListener(new View.OnFocusChangeListener() {

@Override

public void onFocusChange(View v, boolean hasFocus) {

AutoCompleteTextView view = (AutoCompleteTextView) v;

if (hasFocus) {//出现历史输入记录

view.showDropDown();

}

}

});

}

/**

  • 把指定AutoCompleteTextView中内容保存到sharedPreference中指定的字符段

  • 每次输入完之后调用此方法保存输入的值到缓存里

  • @param field 保存在sharedPreference中的字段名

  • @param autoCompleteTextView 要操作的AutoCompleteTextView

*/

private void saveHistory(String field, AutoCompleteTextView autoCompleteTextView) {

String text = autoCompleteTextView.getText().toString();//输入的值

SharedPreferences sp = getSharedPreferences(“sp_history”, 0);

String tvHistory = sp.getString(field, “深圳”);

if (!tvHistory.contains(text + “,”)) {//如果历史缓存中不存在输入的值则

StringBuilder sb = new StringBuilder(tvHistory);

sb.insert(0, text + “,”);

sp.edit().putString(“history”, sb.toString()).commit();//写入缓存

}

}

然后就是使用这两个方法了。

在这里插入图片描述

在点击软件盘搜索的时候,进行输入值的保存,然后在initData里面调用初始化方法

那么现在运行一下

在这里插入图片描述

OK,下面就要实现另一个功能了,就是搜索记录的动态布局展示,这个地方跟淘宝的那个搜索有点相似,实现这个功能需要自定义一个控件,还有样式,会比较麻烦,请一步一步来看。

这个样式和自定义控件的代码我都会放在mvplibrary下,首先是样式

在这里插入图片描述

样式代码:

接下来是自定义控件,我在view包下又建了一个flowlayout包,这个用于防止自定义控件需要用到的代码,这个代码来源于网络,并不是我自己敲出来的,这里我说明一下,以免造成不必要的麻烦,你只管复制粘贴即可。

在这里插入图片描述

FlowLayout

package com.llw.mvplibrary.view.flowlayout;

import android.content.Context;

import android.content.res.TypedArray;

import android.util.AttributeSet;

import android.util.LayoutDirection;

import android.view.View;

import android.view.ViewGroup;

import androidx.core.text.TextUtilsCompat;

import com.llw.mvplibrary.R;

import java.util.ArrayList;

import java.util.Collections;

import java.util.List;

import java.util.Locale;

public class FlowLayout extends ViewGroup {

private static final String TAG = “FlowLayout”;

private static final int LEFT = -1;

private static final int CENTER = 0;

private static final int RIGHT = 1;

private int limitLineCount; //默认显示3行 断词条显示3行,长词条显示2行

private boolean isLimit; //是否有行限制

private boolean isOverFlow; //是否溢出2行

private int mGravity;

protected List<List> mAllViews = new ArrayList<List>();

protected List mLineHeight = new ArrayList();

protected List mLineWidth = new ArrayList();

private List lineViews = new ArrayList<>();

public boolean isOverFlow() {

return isOverFlow;

}

private void setOverFlow(boolean overFlow) {

isOverFlow = overFlow;

}

public boolean isLimit() {

return isLimit;

}

public void setLimit(boolean limit) {

if (!limit) {

setOverFlow(false);

}

isLimit = limit;

}

public FlowLayout(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TagFlowLayout);

mGravity = ta.getInt(R.styleable.TagFlowLayout_tag_gravity, LEFT);

limitLineCount = ta.getInt(R.styleable.TagFlowLayout_limit_line_count, 3);

isLimit = ta.getBoolean(R.styleable.TagFlowLayout_is_limit, false);

int layoutDirection = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault());

if (layoutDirection == LayoutDirection.RTL) {

if (mGravity == LEFT) {

mGravity = RIGHT;

} else {

mGravity = LEFT;

}

}

ta.recycle();

}

public FlowLayout(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public FlowLayout(Context context) {

this(context, null);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);

int modeWidth = MeasureSpec.getMode(widthMeasureSpec);

int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

// wrap_content

int width = 0;

int height = 0;

int lineWidth = 0;

int lineHeight = 0;

//在每一次换行之后记录,是否超过了行数

int lineCount = 0;//记录当前的行数

int cCount = getChildCount();

for (int i = 0; i < cCount; i++) {

View child = getChildAt(i);

if (child.getVisibility() == View.GONE) {

if (i == cCount - 1) {

if (isLimit) {

if (lineCount == limitLineCount) {

setOverFlow(true);

break;

} else {

setOverFlow(false);

}

}

width = Math.max(lineWidth, width);

height += lineHeight;

lineCount++;

}

continue;

}

measureChild(child, widthMeasureSpec, heightMeasureSpec);

MarginLayoutParams lp = (MarginLayoutParams) child

.getLayoutParams();

int childWidth = child.getMeasuredWidth() + lp.leftMargin

  • lp.rightMargin;

int childHeight = child.getMeasuredHeight() + lp.topMargin

  • lp.bottomMargin;

if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) {

if (isLimit) {

if (lineCount == limitLineCount) {

setOverFlow(true);

break;

} else {

setOverFlow(false);

}

}

width = Math.max(width, lineWidth);

lineWidth = childWidth;

height += lineHeight;

lineHeight = childHeight;

lineCount++;

} else {

lineWidth += childWidth;

lineHeight = Math.max(lineHeight, childHeight);

}

if (i == cCount - 1) {

if (isLimit) {

if (lineCount == limitLineCount) {

setOverFlow(true);

break;

} else {

setOverFlow(false);

}

}

width = Math.max(lineWidth, width);

height += lineHeight;

lineCount++;

}

}

setMeasuredDimension(

modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),

modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom()//

);

}

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

mAllViews.clear();

mLineHeight.clear();

mLineWidth.clear();

lineViews.clear();

int width = getWidth();

int lineWidth = 0;

int lineHeight = 0;

//如果超过规定的行数则不进行绘制

int lineCount = 0;//记录当前的行数

int cCount = getChildCount();

for (int i = 0; i < cCount; i++) {

View child = getChildAt(i);

if (child.getVisibility() == View.GONE) continue;

MarginLayoutParams lp = (MarginLayoutParams) child

.getLayoutParams();

int childWidth = child.getMeasuredWidth();

int childHeight = child.getMeasuredHeight();

if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width - getPaddingLeft() - getPaddingRight()) {

if (isLimit) {

if (lineCount == limitLineCount) {

break;

}

}

mLineHeight.add(lineHeight);

mAllViews.add(lineViews);

mLineWidth.add(lineWidth);

lineWidth = 0;

lineHeight = childHeight + lp.topMargin + lp.bottomMargin;

lineViews = new ArrayList();

lineCount++;

}

lineWidth += childWidth + lp.leftMargin + lp.rightMargin;

lineHeight = Math.max(lineHeight, childHeight + lp.topMargin

  • lp.bottomMargin);

lineViews.add(child);

}

mLineHeight.add(lineHeight);

mLineWidth.add(lineWidth);

mAllViews.add(lineViews);

int left = getPaddingLeft();

int top = getPaddingTop();

int lineNum = mAllViews.size();

for (int i = 0; i < lineNum; i++) {

lineViews = mAllViews.get(i);

lineHeight = mLineHeight.get(i);

// set gravity

int currentLineWidth = this.mLineWidth.get(i);

switch (this.mGravity) {

case LEFT:

left = getPaddingLeft();

break;

case CENTER:

left = (width - currentLineWidth) / 2 + getPaddingLeft();

break;

case RIGHT:

// 适配了rtl,需要补偿一个padding值

left = width - (currentLineWidth + getPaddingLeft()) - getPaddingRight();

// 适配了rtl,需要把lineViews里面的数组倒序排

Collections.reverse(lineViews);

break;

}

for (int j = 0; j < lineViews.size(); j++) {

View child = lineViews.get(j);

if (child.getVisibility() == View.GONE) {

continue;

}

MarginLayoutParams lp = (MarginLayoutParams) child

.getLayoutParams();

int lc = left + lp.leftMargin;

int tc = top + lp.topMargin;

int rc = lc + child.getMeasuredWidth();

int bc = tc + child.getMeasuredHeight();

child.layout(lc, tc, rc, bc);

left += child.getMeasuredWidth() + lp.leftMargin

  • lp.rightMargin;

}

top += lineHeight;

}

}

@Override

public LayoutParams generateLayoutParams(AttributeSet attrs) {

return new MarginLayoutParams(getContext(), attrs);

}

@Override

protected LayoutParams generateDefaultLayoutParams() {

return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

}

@Override

protected LayoutParams generateLayoutParams(LayoutParams p) {

return new MarginLayoutParams§;

}

}

RecordsDao

package com.llw.mvplibrary.view.flowlayout;

import android.annotation.SuppressLint;

import android.content.ContentValues;

import android.content.Context;

import android.database.Cursor;

import android.database.SQLException;

import android.database.sqlite.SQLiteDatabase;

import android.util.Log;

import java.text.SimpleDateFormat;

import java.util.ArrayList;

import java.util.Date;

import java.util.List;

/**

  • 历史记录搜索操作类

*/

public class RecordsDao {

private final String TABLE_NAME = “records”;

private SQLiteDatabase recordsDb;

private RecordSQLiteOpenHelper recordHelper;

private NotifyDataChanged mNotifyDataChanged;

private String mUsername;

public RecordsDao(Context context, String username) {

recordHelper = new RecordSQLiteOpenHelper(context);

mUsername = username;

}

public interface NotifyDataChanged {

void notifyDataChanged();

}

/**

  • 设置数据变化监听

*/

public void setNotifyDataChanged(NotifyDataChanged notifyDataChanged) {

mNotifyDataChanged = notifyDataChanged;

}

/**

  • 移除数据变化监听

*/

public void removeNotifyDataChanged() {

if (mNotifyDataChanged != null) {

mNotifyDataChanged = null;

}

}

private synchronized SQLiteDatabase getWritableDatabase() {

return recordHelper.getWritableDatabase();

}

private synchronized SQLiteDatabase getReadableDatabase() {

return recordHelper.getReadableDatabase();

}

/**

  • 如果考虑操作频繁可以到最后不用数据库时关闭

  • 关闭数据库

*/

public void closeDatabase() {

if (recordsDb != null) {

recordsDb.close();

}

}

/**

  • 添加搜索记录

  • @param record 记录

*/

public void addRecords(String record) {

//如果这条记录没有则添加,有则更新时间

int recordId = getRecordId(record);

try {

recordsDb = getReadableDatabase();

if (-1 == recordId) {

ContentValues values = new ContentValues();

values.put(“username”, mUsername);

values.put(“keyword”, record);

//添加搜索记录

recordsDb.insert(TABLE_NAME, null, values);

} else {

Date d = new Date();

@SuppressLint(“SimpleDateFormat”) SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);

//更新搜索历史数据时间

ContentValues values = new ContentValues();

values.put(“time”, sdf.format(d));

recordsDb.update(TABLE_NAME, values, “_id = ?”, new String[]{Integer.toString(recordId)});

}

if (mNotifyDataChanged != null) {

mNotifyDataChanged.notifyDataChanged();

}

} catch (Exception e) {

e.printStackTrace();

}

}

/**

  • 判断是否含有该搜索记录

  • @param record 记录

  • @return true | false

*/

public boolean isHasRecord(String record) {

boolean isHasRecord = false;

Cursor cursor = null;

try {

recordsDb = getReadableDatabase();

cursor = recordsDb.query(TABLE_NAME, null, “username = ?”, new String[]{mUsername}, null, null, null);

while (cursor.moveToNext()) {

if (record.equals(cursor.getString(cursor.getColumnIndexOrThrow(“keyword”)))) {

isHasRecord = true;

}

}

} catch (IllegalArgumentException e) {

e.printStackTrace();

} finally {

if (cursor != null) {

//关闭游标

cursor.close();

}

}

return isHasRecord;

}

/**

  • 判断是否含有该搜索记录

  • @param record 记录

  • @return id

*/

public int getRecordId(String record) {

int isHasRecord = -1;

Cursor cursor = null;

try {

recordsDb = getReadableDatabase();

cursor = recordsDb.query(TABLE_NAME, null, “username = ?”, new String[]{mUsername}, null, null, null);

while (cursor.moveToNext()) {

if (record.equals(cursor.getString(cursor.getColumnIndexOrThrow(“keyword”)))) {

isHasRecord = cursor.getInt(cursor.getColumnIndexOrThrow(“_id”));

}

}

} catch (IllegalArgumentException e) {

e.printStackTrace();

} finally {

if (cursor != null) {

//关闭游标

cursor.close();

}

}

return isHasRecord;

}

/**

  • 获取当前用户全部搜索记录

  • @return 记录集合

*/

public List getRecordsList() {

List recordsList = new ArrayList<>();

Cursor cursor = null;

try {

recordsDb = getReadableDatabase();

cursor = recordsDb.query(TABLE_NAME, null, “username = ?”, new String[]{mUsername}, null, null, “time desc”);

while (cursor.moveToNext()) {

String name = cursor.getString(cursor.getColumnIndexOrThrow(“keyword”));

recordsList.add(name);

}

} catch (IllegalArgumentException e) {

e.printStackTrace();

} finally {

if (cursor != null) {

//关闭游标

cursor.close();

}

}

return recordsList;

}

/**

  • 获取指定数量搜索记录

  • @return 记录集合

*/

public List getRecordsByNumber(int recordNumber) {

List recordsList = new ArrayList<>();

if (recordNumber < 0) {

throw new IllegalArgumentException();

} else if (0 == recordNumber) {

return recordsList;

} else {

Cursor cursor = null;

try {

recordsDb = getReadableDatabase();

cursor = recordsDb.query(TABLE_NAME, null, “username = ?”, new String[]{mUsername}, null, null, "time desc limit " + recordNumber);

while (cursor.moveToNext()) {

String name = cursor.getString(cursor.getColumnIndexOrThrow(“keyword”));

recordsList.add(name);

}

} catch (IllegalArgumentException e) {

e.printStackTrace();

} finally {

if (cursor != null) {

//关闭游标

cursor.close();

}

}

}

return recordsList;

}

/**

  • 模糊查询

  • @param record 记录

  • @return 返回类似记录

*/

public List querySimlarRecord(String record) {

List similarRecords = new ArrayList<>();

Cursor cursor = null;

try {

recordsDb = getReadableDatabase();

cursor = recordsDb.query(TABLE_NAME, null, “username = ? and keyword like ‘%?%’”, new String[]{mUsername, record}, null, null, “order by time desc”);

while (cursor.moveToNext()) {

String name = cursor.getString(cursor.getColumnIndexOrThrow(“keyword”));

similarRecords.add(name);

}

} catch (IllegalArgumentException e) {

e.printStackTrace();

} finally {

if (cursor != null) {

//关闭游标

cursor.close();

}

}

return similarRecords;

}

/**

  • 清除指定用户的搜索记录

*/

public void deleteUsernameAllRecords() {

try {

recordsDb = getWritableDatabase();

recordsDb.delete(TABLE_NAME, “username = ?”, new String[]{mUsername});

if (mNotifyDataChanged != null) {

mNotifyDataChanged.notifyDataChanged();

}

} catch (SQLException e) {

e.printStackTrace();

Log.e(TABLE_NAME, “清除所有历史记录失败”);

} finally {

}

}

/**

  • 清空数据库所有的历史记录

*/

public void deleteAllRecords() {

try {

recordsDb = getWritableDatabase();

recordsDb.execSQL("delete from " + TABLE_NAME);

if (mNotifyDataChanged != null) {

mNotifyDataChanged.notifyDataChanged();

}

} catch (SQLException e) {

e.printStackTrace();

Log.e(TABLE_NAME, “清除所有历史记录失败”);

} finally {

}

}

/**

  • 通过id删除记录

  • @param id 记录id

  • @return 返回删除id

*/

public int deleteRecord(int id) {

int d = -1;

try {

recordsDb = getWritableDatabase();

d = recordsDb.delete(TABLE_NAME, “_id = ?”, new String[]{Integer.toString(id)});

if (mNotifyDataChanged != null) {

mNotifyDataChanged.notifyDataChanged();

}

} catch (Exception e) {

e.printStackTrace();

Log.e(TABLE_NAME, “删除_id:” + id + “历史记录失败”);

}

return d;

}

/**

  • 通过记录删除记录

  • @param record 记录

*/

public int deleteRecord(String record) {

int recordId = -1;

try {

recordsDb = getWritableDatabase();

recordId = recordsDb.delete(TABLE_NAME, “username = ? and keyword = ?”, new String[]{mUsername, record});

if (mNotifyDataChanged != null) {

mNotifyDataChanged.notifyDataChanged();

}

} catch (SQLException e) {

e.printStackTrace();

Log.e(TABLE_NAME, “清除所有历史记录失败”);

}

return recordId;

}

}

RecordSQLiteOpenHelper

package com.llw.mvplibrary.view.flowlayout;

import android.content.Context;

import android.database.sqlite.SQLiteDatabase;

import android.database.sqlite.SQLiteOpenHelper;

/**

  • 数据库帮助类

*/

public class RecordSQLiteOpenHelper extends SQLiteOpenHelper {

private final static String DB_NAME = “search_history.db”;

private final static int DB_VERSION = 1;

public RecordSQLiteOpenHelper(Context context) {

super(context, DB_NAME, null, DB_VERSION);

}

@Override

public void onCreate(SQLiteDatabase db) {

String sqlStr = “CREATE TABLE IF NOT EXISTS records (_id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, keyword TEXT, time NOT NULL DEFAULT (datetime(‘now’,‘localtime’)));”;

db.execSQL(sqlStr);

}

@Override

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}

}

TagAdapter

package com.llw.mvplibrary.view.flowlayout;

import android.util.Log;

import android.view.View;

import java.util.ArrayList;

import java.util.Arrays;

import java.util.HashSet;

import java.util.List;

import java.util.Set;

/**

  • 布局适配器

*/

public abstract class TagAdapter {

private List mTagData;

private OnDataChangedListener mOnDataChangedListener;

@Deprecated

private HashSet mCheckedPosList = new HashSet();

public TagAdapter(List datas) {

mTagData = datas;

}

public void setData(List datas) {

mTagData = datas;

}

@Deprecated

public TagAdapter(T[] datas) {

mTagData = new ArrayList(Arrays.asList(datas));

}

interface OnDataChangedListener {

void onChanged();

}

void setOnDataChangedListener(OnDataChangedListener listener) {

mOnDataChangedListener = listener;

}

@Deprecated

public void setSelectedList(int… poses) {

Set set = new HashSet<>();

for (int pos : poses) {

set.add(pos);

}

setSelectedList(set);

}

@Deprecated

public void setSelectedList(Set set) {

mCheckedPosList.clear();

if (set != null) {

mCheckedPosList.addAll(set);

}

notifyDataChanged();

}

@Deprecated

HashSet getPreCheckedList() {

return mCheckedPosList;

}

public int getCount() {

return mTagData == null ? 0 : mTagData.size();

}

public void notifyDataChanged() {

if (mOnDataChangedListener != null)

mOnDataChangedListener.onChanged();

}

public T getItem(int position) {

return mTagData.get(position);

}

public abstract View getView(FlowLayout parent, int position, T t);

public void onSelected(int position, View view) {

Log.d(“llw”, "onSelected " + position);

}

public void unSelected(int position, View view) {

Log.d(“llw”, "unSelected " + position);

}

public boolean setSelected(int position, T t) {

return false;

}

}

TagFlowLayout

package com.llw.mvplibrary.view.flowlayout;

import android.content.Context;

import android.content.res.TypedArray;

import android.os.Bundle;

import android.os.Parcelable;

import android.text.TextUtils;

import android.util.AttributeSet;

import android.util.Log;

import android.view.View;

import android.view.ViewGroup;

import com.llw.mvplibrary.R;

import java.util.HashSet;

import java.util.Iterator;

import java.util.Set;

/**

  • 自定义控件

*/

public class TagFlowLayout extends FlowLayout

implements TagAdapter.OnDataChangedListener {

private static final String TAG = “TagFlowLayout”;

private TagAdapter mTagAdapter;

private int mSelectedMax = -1;//-1为不限制数量

private Set mSelectedView = new HashSet();

private OnSelectListener mOnSelectListener;

private OnTagClickListener mOnTagClickListener;

private OnLongClickListener mOnLongClickListener;

public interface OnSelectListener {

void onSelected(Set selectPosSet);

}

public interface OnTagClickListener {

void onTagClick(View view, int position, FlowLayout parent);

}

public interface OnLongClickListener {

void onLongClick(View view, int position);

}

public TagFlowLayout(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TagFlowLayout);

mSelectedMax = ta.getInt(R.styleable.TagFlowLayout_max_select, -1);

ta.recycle();

}

public TagFlowLayout(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public TagFlowLayout(Context context) {

this(context, null);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int cCount = getChildCount();

for (int i = 0; i < cCount; i++) {

TagView tagView = (TagView) getChildAt(i);

if (tagView.getVisibility() == View.GONE) {

continue;

}

if (tagView.getTagView().getVisibility() == View.GONE) {

tagView.setVisibility(View.GONE);

}

}

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

public void setOnSelectListener(OnSelectListener onSelectListener) {

mOnSelectListener = onSelectListener;

}

public void setOnTagClickListener(OnTagClickListener onTagClickListener) {

mOnTagClickListener = onTagClickListener;

}

public void setOnLongClickListener(OnLongClickListener onLongClickListener) {

mOnLongClickListener = onLongClickListener;

}

public void setAdapter(TagAdapter adapter) {

mTagAdapter = adapter;

mTagAdapter.setOnDataChangedListener(this);

mSelectedView.clear();

changeAdapter();

}

@SuppressWarnings(“ResourceType”)

private void changeAdapter() {

removeAllViews();

TagAdapter adapter = mTagAdapter;

TagView tagViewContainer = null;

HashSet preCheckedList = mTagAdapter.getPreCheckedList();

for (int i = 0; i < adapter.getCount(); i++) {

View tagView = adapter.getView(this, i, adapter.getItem(i));

tagViewContainer = new TagView(getContext());

tagView.setDuplicateParentStateEnabled(true);

if (tagView.getLayoutParams() != null) {

tagViewContainer.setLayoutParams(tagView.getLayoutParams());

} else {

ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(

ViewGroup.LayoutParams.WRAP_CONTENT,

ViewGroup.LayoutParams.WRAP_CONTENT);

lp.setMargins(dip2px(getContext(), 5),

dip2px(getContext(), 5),

dip2px(getContext(), 5),

dip2px(getContext(), 5));

tagViewContainer.setLayoutParams(lp);

}

ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

tagView.setLayoutParams(lp);

tagViewContainer.addView(tagView);

addView(tagViewContainer);

if (preCheckedList.contains(i)) {

setChildChecked(i, tagViewContainer);

}

if (mTagAdapter.setSelected(i, adapter.getItem(i))) {

setChildChecked(i, tagViewContainer);

}

tagView.setClickable(false);

final TagView finalTagViewContainer = tagViewContainer;

final int position = i;

tagViewContainer.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

doSelect(finalTagViewContainer, position);

if (mOnTagClickListener != null) {

mOnTagClickListener.onTagClick(finalTagViewContainer, position,

TagFlowLayout.this);

}

}

});

tagViewContainer.setOnLongClickListener(new View.OnLongClickListener() {

@Override

public boolean onLongClick(View v) {

if (mOnLongClickListener != null) {

mOnLongClickListener.onLongClick(finalTagViewContainer, position);

//消费事件,不让事件继续下去

return true;

}

return false;

}

});

}

mSelectedView.addAll(preCheckedList);

}

public void setMaxSelectCount(int count) {

if (mSelectedView.size() > count) {

Log.w(TAG, “you has already select more than " + count + " views , so it will be clear .”);

mSelectedView.clear();

}

mSelectedMax = count;

}

public Set getSelectedList() {

return new HashSet(mSelectedView);

}

private void setChildChecked(int position, TagView view) {

view.setChecked(true);

mTagAdapter.onSelected(position, view.getTagView());

}

private void setChildUnChecked(int position, TagView view) {

view.setChecked(false);

mTagAdapter.unSelected(position, view.getTagView());

}

private void doSelect(TagView child, int position) {

if (!child.isChecked()) {

//处理max_select=1的情况

if (mSelectedMax == 1 && mSelectedView.size() == 1) {

Iterator iterator = mSelectedView.iterator();

Integer preIndex = iterator.next();

TagView pre = (TagView) getChildAt(preIndex);

setChildUnChecked(preIndex, pre);

setChildChecked(position, child);

mSelectedView.remove(preIndex);

mSelectedView.add(position);

} else {

if (mSelectedMax > 0 && mSelectedView.size() >= mSelectedMax) {

return;

}

setChildChecked(position, child);

mSelectedView.add(position);

}

} else {

setChildUnChecked(position, child);

mSelectedView.remove(position);

}

if (mOnSelectListener != null) {

mOnSelectListener.onSelected(new HashSet(mSelectedView));

}

}

public TagAdapter getAdapter() {

return mTagAdapter;

}

private static final String KEY_CHOOSE_POS = “key_choose_pos”;

private static final String KEY_DEFAULT = “key_default”;

@Override

protected Parcelable onSaveInstanceState() {

Bundle bundle = new Bundle();

bundle.putParcelable(KEY_DEFAULT, super.onSaveInstanceState());

String selectPos = “”;

if (mSelectedView.size() > 0) {

for (int key : mSelectedView) {

selectPos += key + “|”;

}

selectPos = selectPos.substring(0, selectPos.length() - 1);

}

bundle.putString(KEY_CHOOSE_POS, selectPos);

return bundle;

}

@Override

protected void onRestoreInstanceState(Parcelable state) {

if (state instanceof Bundle) {

Bundle bundle = (Bundle) state;

String mSelectPos = bundle.getString(KEY_CHOOSE_POS);

if (!TextUtils.isEmpty(mSelectPos)) {

String[] split = mSelectPos.split(“\|”);

for (String pos : split) {

int index = Integer.parseInt(pos);

mSelectedView.add(index);

TagView tagView = (TagView) getChildAt(index);

if (tagView != null) {

setChildChecked(index, tagView);

}

}

}

super.onRestoreInstanceState(bundle.getParcelable(KEY_DEFAULT));

return;

}

super.onRestoreInstanceState(state);

}

@Override

public void onChanged() {

mSelectedView.clear();

changeAdapter();

}

public static int dip2px(Context context, float dpValue) {

final float scale = context.getResources().getDisplayMetrics().density;

return (int) (dpValue * scale + 0.5f);

}

}

TagView

package com.llw.mvplibrary.view.flowlayout;

import android.content.Context;

import android.view.View;

import android.widget.Checkable;

import android.widget.FrameLayout;

/**

  • 自定义控件

*/

public class TagView extends FrameLayout implements Checkable {

private boolean isChecked;

private static final int[] CHECK_STATE = new int[]{android.R.attr.state_checked};

public TagView(Context context) {

super(context);

}

public View getTagView() {

return getChildAt(0);

}

@Override

public int[] onCreateDrawableState(int extraSpace) {

int[] states = super.onCreateDrawableState(extraSpace + 1);

if (isChecked()) {

mergeDrawableStates(states, CHECK_STATE);

}

return states;

}

/**

  • @param checked The new checked state

*/

@Override

public void setChecked(boolean checked) {

if (this.isChecked != checked) {

this.isChecked = checked;

refreshDrawableState();

}

}

/**

  • @return The current checked state of the view

*/

@Override

public boolean isChecked() {

return isChecked;

}

/**

  • Change the checked state of the view to the inverse of its current state

*/

@Override

public void toggle() {

setChecked(!isChecked);

}

}

现在创建布局和样式

shape_gray_bg_16.xml

<?xml version="1.0" encoding="utf-8"?>

tv_history.xml

<?xml version="1.0" encoding="utf-8"?>

<TextView xmlns:android=“http://schemas.android.com/apk/res/android”

android:layout_width=“wrap_content”

android:gravity=“center”

android:layout_height=“@dimen/dp_30”

android:paddingLeft=“@dimen/dp_12”

android:paddingRight=“@dimen/dp_12”

android:paddingTop=“@dimen/dp_4”

android:paddingBottom=“@dimen/dp_4”

android:layout_margin=“5dp”

android:background=“@drawable/shape_gray_bg_16”

android:singleLine=“true”

android:text=“搜索历史”

android:textColor=“@color/black”

android:textSize=“14sp”/>

然后在activity_search_city.xml中增加历史记录布局的代码

在这里插入图片描述

布局中用到了两个图标,分别是

icon_bottom.png

在这里插入图片描述

icon_delete_history.png

在这里插入图片描述

然后是历史搜索的布局代码

<LinearLayout

android:id=“@+id/ll_history_content”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:layout_marginTop=“@dimen/dp_4”

android:layout_marginBottom=“@dimen/dp_8”

android:background=“@color/white”

android:orientation=“vertical”

android:paddingLeft=“@dimen/dp_16”

android:paddingTop=“@dimen/dp_8”

android:paddingRight=“@dimen/dp_16”>

<LinearLayout

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:gravity=“center_vertical”

android:orientation=“horizontal”>

<TextView

android:layout_width=“0dp”

android:layout_height=“wrap_content”

android:layout_weight=“1”

android:text=“搜索历史”

android:textColor=“@color/black”

android:textSize=“@dimen/sp_16” />

<ImageView

android:id=“@+id/clear_all_records”

android:layout_width=“@dimen/dp_24”

android:layout_height=“@dimen/dp_24”

android:background=“@mipmap/icon_delete_history” />

<com.llw.mvplibrary.view.flowlayout.TagFlowLayout

android:id=“@+id/fl_search_records”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:paddingTop=“@dimen/dp_8”

app:is_limit=“true”

app:limit_line_count=“3”

app:max_select=“1” />

<ImageView

android:id=“@+id/iv_arrow”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:src=“@mipmap/icon_bottom”

android:visibility=“gone” />

布局有了,下面就是写代码了,

然后就是在SearchCity中使用了

首先绑定视图

@BindView(R.id.clear_all_records)

ImageView clearAllRecords;//清理所有历史记录

@BindView(R.id.fl_search_records)

TagFlowLayout flSearchRecords;//搜索历史布局

@BindView(R.id.iv_arrow)

ImageView ivArrow;//超过三行就会出现,展开显示更多

@BindView(R.id.ll_history_content)

LinearLayout llHistoryContent;//搜索历史主布局

然后编写代码

在这里插入图片描述

我把之前初始化列表数据的代码也放到这个initView里面了,下面我贴一下代码

private void initView() {

//默认账号

String username = “007”;

//初始化数据库

mRecordsDao = new RecordsDao(this, username);

initTagFlowLayout();

//创建历史标签适配器

//为标签设置对应的内容

mRecordsAdapter = new TagAdapter(recordList) {

@Override

public View getView(FlowLayout parent, int position, String s) {

TextView tv = (TextView) LayoutInflater.from(context).inflate(R.layout.tv_history,

flSearchRecords, false);

//为标签设置对应的内容

tv.setText(s);

return tv;

}

};

editQuery.addTextChangedListener(textWatcher);//添加输入监听

editQuery.setOnEditorActionListener(new TextView.OnEditorActionListener() {

@Override

public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {

if (actionId == EditorInfo.IME_ACTION_SEARCH) {

String location = editQuery.getText().toString();

if (!TextUtils.isEmpty(location)) {

showLoadingDialog();

//添加数据

mRecordsDao.addRecords(location);

mPresent.searchCity(context, location);

//数据保存

saveHistory(“history”, editQuery);

} else {

ToastUtils.showShortToast(context, “请输入搜索关键词”);

}

}

return false;

}

});

flSearchRecords.setAdapter(mRecordsAdapter);

flSearchRecords.setOnTagClickListener(new TagFlowLayout.OnTagClickListener() {

@Override

public void onTagClick(View view, int position, FlowLayout parent) {

//清空editText之前的数据

editQuery.setText(“”);

//将获取到的字符串传到搜索结果界面,点击后搜索对应条目内容

editQuery.setText(recordList.get(position));

editQuery.setSelection(editQuery.length());

}

});

//长按删除某个条目

flSearchRecords.setOnLongClickListener(new TagFlowLayout.OnLongClickListener() {

@Override

public void onLongClick(View view, final int position) {

showDialog(“确定要删除该条历史记录?”, new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

//删除某一条记录

mRecordsDao.deleteRecord(recordList.get(position));

initTagFlowLayout();

}

});

}

});

//view加载完成时回调

flSearchRecords.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

@Override

public void onGlobalLayout() {

boolean isOverFlow = flSearchRecords.isOverFlow();

boolean isLimit = flSearchRecords.isLimit();

if (isLimit && isOverFlow) {

ivArrow.setVisibility(View.VISIBLE);

} else {

ivArrow.setVisibility(View.GONE);

}

}

});

//初始化搜索返回的数据列表

mAdapter = new SearchCityAdapter(R.layout.item_search_city_list, mList);

rv.setLayoutManager(new LinearLayoutManager(context));

rv.setAdapter(mAdapter);

mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {

@Override

public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {

SPUtils.putString(Constant.LOCATION, mList.get(position).getLocation(), context);

//发送消息

EventBus.getDefault().post(new SearchCityEvent(mList.get(position).getLocation(),

mList.get(position).getParent_city()));

finish();

}

});

}

//历史记录布局

private void initTagFlowLayout() {

Observable.create(new ObservableOnSubscribe<List>() {

@Override

public void subscribe(ObservableEmitter<List> emitter) throws Exception {

emitter.onNext(mRecordsDao.getRecordsByNumber(DEFAULT_RECORD_NUMBER));

}

}).subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.subscribe(new Consumer<List>() {

@Override

public void accept(List s) throws Exception {

recordList.clear();

recordList = s;

if (null == recordList || recordList.size() == 0) {

llHistoryContent.setVisibility(View.GONE);

} else {

llHistoryContent.setVisibility(View.VISIBLE);

}

if (mRecordsAdapter != null) {

mRecordsAdapter.setData(recordList);

mRecordsAdapter.notifyDataChanged();

}

}

});

}

这里面还有一个提示弹窗

//提示弹窗 后续我可能会改,因为原生的太丑了

private void showDialog(String dialogTitle, @NonNull DialogInterface.OnClickListener onClickListener) {

AlertDialog.Builder builder = new AlertDialog.Builder(context);

builder.setMessage(dialogTitle);

builder.setPositiveButton(“确定”, onClickListener);

builder.setNegativeButton(“取消”, null);

builder.create().show();

}

当然还有点击事件也要修改

//点击事件

@OnClick({R.id.iv_clear_search,R.id.clear_all_records, R.id.iv_arrow})

public void onViewClicked(View view) {

switch (view.getId()) {

case R.id.iv_clear_search://清空输入的内容

ivClearSearch.setVisibility(View.GONE);

editQuery.setText(“”);

break;

case R.id.clear_all_records://清除所有记录

showDialog(“确定要删除全部历史记录?”, new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

flSearchRecords.setLimit(true);

//清除所有数据

mRecordsDao.deleteUsernameAllRecords();

llHistoryContent.setVisibility(View.GONE);

}

});

break;

case R.id.iv_arrow://向下展开

flSearchRecords.setLimit(false);

mRecordsAdapter.notifyDataChanged();

break;

}

}

这个点击事件的代码你可以把原来的点击事件直接覆盖掉,

为了不造成误会,我再粘贴一下SearchCityActivity的代码

package com.llw.goodweather.ui;

import android.content.DialogInterface;

import android.content.Intent;

import android.content.SharedPreferences;

import android.os.Bundle;

import android.text.Editable;

import android.text.TextUtils;

import android.text.TextWatcher;

import android.view.KeyEvent;

import android.view.LayoutInflater;

import android.view.View;

import android.view.ViewTreeObserver;

import android.view.inputmethod.EditorInfo;

import android.widget.ArrayAdapter;

import android.widget.AutoCompleteTextView;

import android.widget.ImageView;

import android.widget.LinearLayout;

import android.widget.TextView;

import androidx.annotation.NonNull;

import androidx.appcompat.app.AlertDialog;

import androidx.appcompat.widget.Toolbar;

import androidx.recyclerview.widget.LinearLayoutManager;

import androidx.recyclerview.widget.RecyclerView;

import com.chad.library.adapter.base.BaseQuickAdapter;

import com.llw.goodweather.R;

import com.llw.goodweather.adapter.SearchCityAdapter;

import com.llw.goodweather.bean.SearchCityResponse;

import com.llw.goodweather.contract.SearchCityContract;

import com.llw.goodweather.eventbus.SearchCityEvent;

import com.llw.goodweather.utils.CodeToStringUtils;

import com.llw.goodweather.utils.Constant;

import com.llw.goodweather.utils.SPUtils;

import com.llw.goodweather.utils.StatusBarUtil;

import com.llw.goodweather.utils.ToastUtils;

import com.llw.mvplibrary.mvp.MvpActivity;

import com.llw.mvplibrary.view.flowlayout.FlowLayout;

import com.llw.mvplibrary.view.flowlayout.RecordsDao;

import com.llw.mvplibrary.view.flowlayout.TagAdapter;

import com.llw.mvplibrary.view.flowlayout.TagFlowLayout;

import org.greenrobot.eventbus.EventBus;

import java.util.ArrayList;

import java.util.List;

import butterknife.BindView;

import butterknife.ButterKnife;

import butterknife.OnClick;

import io.reactivex.Observable;

import io.reactivex.ObservableEmitter;

import io.reactivex.ObservableOnSubscribe;

import io.reactivex.android.schedulers.AndroidSchedulers;

import io.reactivex.functions.Consumer;

import io.reactivex.schedulers.Schedulers;

import retrofit2.Response;

import static com.llw.mvplibrary.utils.RecyclerViewAnimation.runLayoutAnimation;

/**

  • 搜索城市

*/

public class SearchCityActivity extends MvpActivity<SearchCityContract.SearchCityPresenter>

implements SearchCityContract.ISearchCityView {

@BindView(R.id.edit_query)

AutoCompleteTextView editQuery;//输入框

@BindView(R.id.iv_clear_search)

ImageView ivClearSearch;//清空输入的内容图标

@BindView(R.id.toolbar)

Toolbar toolbar;

@BindView(R.id.rv)

RecyclerView rv;//数据显示列表

@BindView(R.id.clear_all_records)

ImageView clearAllRecords;//清理所有历史记录

@BindView(R.id.fl_search_records)

TagFlowLayout flSearchRecords;//搜索历史布局

@BindView(R.id.iv_arrow)

ImageView ivArrow;//超过三行就会出现,展开显示更多

@BindView(R.id.ll_history_content)

LinearLayout llHistoryContent;//搜索历史主布局

List<SearchCityResponse.HeWeather6Bean.BasicBean> mList = new ArrayList<>();//数据源

SearchCityAdapter mAdapter;//适配器

private RecordsDao mRecordsDao;

//默然展示词条个数

private final int DEFAULT_RECORD_NUMBER = 10;

private List recordList = new ArrayList<>();

private TagAdapter mRecordsAdapter;

private LinearLayout mHistoryContent;

@Override

public void initData(Bundle savedInstanceState) {

StatusBarUtil.setStatusBarColor(context, R.color.white);//白色状态栏

StatusBarUtil.StatusBarLightMode(context);//黑色字体

Back(toolbar);

initView();//初始化页面数据

initAutoComplete(“history”, editQuery);

}

private void initView() {

//默认账号

String username = “007”;

//初始化数据库

mRecordsDao = new RecordsDao(this, username);

initTagFlowLayout();

//创建历史标签适配器

//为标签设置对应的内容

mRecordsAdapter = new TagAdapter(recordList) {

@Override

public View getView(FlowLayout parent, int position, String s) {

TextView tv = (TextView) LayoutInflater.from(context).inflate(R.layout.tv_history,

flSearchRecords, false);

//为标签设置对应的内容

tv.setText(s);

return tv;

}

};

editQuery.addTextChangedListener(textWatcher);//添加输入监听

editQuery.setOnEditorActionListener(new TextView.OnEditorActionListener() {

@Override

public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {

if (actionId == EditorInfo.IME_ACTION_SEARCH) {

String location = editQuery.getText().toString();

if (!TextUtils.isEmpty(location)) {

showLoadingDialog();

//添加数据

mRecordsDao.addRecords(location);

mPresent.searchCity(context, location);

//数据保存

saveHistory(“history”, editQuery);

} else {

ToastUtils.showShortToast(context, “请输入搜索关键词”);

}

}

return false;

}

});

flSearchRecords.setAdapter(mRecordsAdapter);

flSearchRecords.setOnTagClickListener(new TagFlowLayout.OnTagClickListener() {

@Override

public void onTagClick(View view, int position, FlowLayout parent) {

//清空editText之前的数据

editQuery.setText(“”);

//将获取到的字符串传到搜索结果界面,点击后搜索对应条目内容

editQuery.setText(recordList.get(position));

editQuery.setSelection(editQuery.length());

}

});

//长按删除某个条目

flSearchRecords.setOnLongClickListener(new TagFlowLayout.OnLongClickListener() {

@Override

public void onLongClick(View view, final int position) {

showDialog(“确定要删除该条历史记录?”, new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

//删除某一条记录

mRecordsDao.deleteRecord(recordList.get(position));

initTagFlowLayout();

}

});

}

});

//view加载完成时回调

flSearchRecords.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

@Override

public void onGlobalLayout() {

boolean isOverFlow = flSearchRecords.isOverFlow();

boolean isLimit = flSearchRecords.isLimit();

if (isLimit && isOverFlow) {

ivArrow.setVisibility(View.VISIBLE);

} else {

ivArrow.setVisibility(View.GONE);

}

}

});

//初始化搜索返回的数据列表

mAdapter = new SearchCityAdapter(R.layout.item_search_city_list, mList);

rv.setLayoutManager(new LinearLayoutManager(context));

rv.setAdapter(mAdapter);

mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {

@Override

public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {

SPUtils.putString(Constant.LOCATION, mList.get(position).getLocation(), context);

//发送消息

EventBus.getDefault().post(new SearchCityEvent(mList.get(position).getLocation(),

mList.get(position).getParent_city()));

finish();

}

});

}

//历史记录布局

private void initTagFlowLayout() {

Observable.create(new ObservableOnSubscribe<List>() {

@Override

public void subscribe(ObservableEmitter<List> emitter) throws Exception {

emitter.onNext(mRecordsDao.getRecordsByNumber(DEFAULT_RECORD_NUMBER));

}

}).subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.subscribe(new Consumer<List>() {

@Override

public void accept(List s) throws Exception {

recordList.clear();

recordList = s;

if (null == recordList || recordList.size() == 0) {

llHistoryContent.setVisibility(View.GONE);

} else {

llHistoryContent.setVisibility(View.VISIBLE);

}

if (mRecordsAdapter != null) {

mRecordsAdapter.setData(recordList);

mRecordsAdapter.notifyDataChanged();

}

}

});

}

/**

  • 使 AutoCompleteTextView在一开始获得焦点时自动提示

  • @param field 保存在sharedPreference中的字段名

  • @param autoCompleteTextView 要操作的AutoCompleteTextView

*/

private void initAutoComplete(String field, AutoCompleteTextView autoCompleteTextView) {

SharedPreferences sp = getSharedPreferences(“sp_history”, 0);

String etHistory = sp.getString(“history”, “深圳”);//获取缓存

String[] histories = etHistory.split(“,”);//通过,号分割成String数组

ArrayAdapter adapter = new ArrayAdapter(this, R.layout.item_tv_history, histories);

// 只保留最近的50条的记录

if (histories.length > 50) {

String[] newHistories = new String[50];

System.arraycopy(histories, 0, newHistories, 0, 50);

adapter = new ArrayAdapter(this, R.layout.item_tv_history, newHistories);

}

//AutoCompleteTextView可以直接设置数据适配器,并且在获得焦点的时候弹出,

//通常是在用户第一次进入页面的时候,点击输入框输入的时候出现,如果每次都出现

//是会应用用户体验的,这里不推荐这么做

autoCompleteTextView.setAdapter(adapter);

autoCompleteTextView.setOnFocusChangeListener(new View.OnFocusChangeListener() {

@Override

public void onFocusChange(View v, boolean hasFocus) {

AutoCompleteTextView view = (AutoCompleteTextView) v;

if (hasFocus) {//出现历史输入记录

view.showDropDown();

}

}

});

}

/**

  • 把指定AutoCompleteTextView中内容保存到sharedPreference中指定的字符段

最后

总而言之,Android开发行业变化太快,作为技术人员就要保持终生学习的态度,让学习力成为核心竞争力,所谓“活到老学到老”只有不断的学习,不断的提升自己,才能跟紧行业的步伐,才能不被时代所淘汰。

在这里我分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

参考docs.qq.com/doc/DSkNLaERkbnFoS0ZF
rchRecords.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

@Override

public void onGlobalLayout() {

boolean isOverFlow = flSearchRecords.isOverFlow();

boolean isLimit = flSearchRecords.isLimit();

if (isLimit && isOverFlow) {

ivArrow.setVisibility(View.VISIBLE);

} else {

ivArrow.setVisibility(View.GONE);

}

}

});

//初始化搜索返回的数据列表

mAdapter = new SearchCityAdapter(R.layout.item_search_city_list, mList);

rv.setLayoutManager(new LinearLayoutManager(context));

rv.setAdapter(mAdapter);

mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {

@Override

public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {

SPUtils.putString(Constant.LOCATION, mList.get(position).getLocation(), context);

//发送消息

EventBus.getDefault().post(new SearchCityEvent(mList.get(position).getLocation(),

mList.get(position).getParent_city()));

finish();

}

});

}

//历史记录布局

private void initTagFlowLayout() {

Observable.create(new ObservableOnSubscribe<List>() {

@Override

public void subscribe(ObservableEmitter<List> emitter) throws Exception {

emitter.onNext(mRecordsDao.getRecordsByNumber(DEFAULT_RECORD_NUMBER));

}

}).subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.subscribe(new Consumer<List>() {

@Override

public void accept(List s) throws Exception {

recordList.clear();

recordList = s;

if (null == recordList || recordList.size() == 0) {

llHistoryContent.setVisibility(View.GONE);

} else {

llHistoryContent.setVisibility(View.VISIBLE);

}

if (mRecordsAdapter != null) {

mRecordsAdapter.setData(recordList);

mRecordsAdapter.notifyDataChanged();

}

}

});

}

/**

  • 使 AutoCompleteTextView在一开始获得焦点时自动提示

  • @param field 保存在sharedPreference中的字段名

  • @param autoCompleteTextView 要操作的AutoCompleteTextView

*/

private void initAutoComplete(String field, AutoCompleteTextView autoCompleteTextView) {

SharedPreferences sp = getSharedPreferences(“sp_history”, 0);

String etHistory = sp.getString(“history”, “深圳”);//获取缓存

String[] histories = etHistory.split(“,”);//通过,号分割成String数组

ArrayAdapter adapter = new ArrayAdapter(this, R.layout.item_tv_history, histories);

// 只保留最近的50条的记录

if (histories.length > 50) {

String[] newHistories = new String[50];

System.arraycopy(histories, 0, newHistories, 0, 50);

adapter = new ArrayAdapter(this, R.layout.item_tv_history, newHistories);

}

//AutoCompleteTextView可以直接设置数据适配器,并且在获得焦点的时候弹出,

//通常是在用户第一次进入页面的时候,点击输入框输入的时候出现,如果每次都出现

//是会应用用户体验的,这里不推荐这么做

autoCompleteTextView.setAdapter(adapter);

autoCompleteTextView.setOnFocusChangeListener(new View.OnFocusChangeListener() {

@Override

public void onFocusChange(View v, boolean hasFocus) {

AutoCompleteTextView view = (AutoCompleteTextView) v;

if (hasFocus) {//出现历史输入记录

view.showDropDown();

}

}

});

}

/**

  • 把指定AutoCompleteTextView中内容保存到sharedPreference中指定的字符段

最后

总而言之,Android开发行业变化太快,作为技术人员就要保持终生学习的态度,让学习力成为核心竞争力,所谓“活到老学到老”只有不断的学习,不断的提升自己,才能跟紧行业的步伐,才能不被时代所淘汰。

在这里我分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

[外链图片转存中…(img-87R6ZVYd-1724319090757)]

[外链图片转存中…(img-tpoE1dFE-1724319090758)]

[外链图片转存中…(img-2fGea5hb-1724319090758)]

还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

参考docs.qq.com/doc/DSkNLaERkbnFoS0ZF

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值