English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Implementation of Search Function Similar to Taobao E-commerce Search, Listening to Soft Keyboard Search Events, Delayed Automatic Search, and Time-Sorted Search History Records

I recently changed jobs to a new company, and the first task I accepted was to implement the search function and search history records of an e-commerce module.

The requirements are roughly similar to those of Taobao and other e-commerce platforms. At the top is a search box, and below it shows the search history records. After entering the keywords to be searched in the EditText, press the search key on the soft keyboard./Automatically search after a delay of xxx ms. Then display the search content to the user./Prompt the user that no relevant information was found.

Historical records are sorted by time, with the most recent at the front. If you enter a previously searched keyword, such as jeans (originally the second item), the time of this record will be updated. Next time you look at it, the arrangement of jeans will be at the first position. And there is a function to clear historical records.

Organize requirements, the general work to be done is as follows:

Functional part:

First, click the EditText to pop up the soft keyboard input method, and the right bottom key is labeled [Search].

Second, listen to the soft keyboard input method's Search event.

Third, after entering content in the EditText,1000ms without modification will automatically trigger the search function.

Four, save the historical records sorted by time,

Five, clear historical records

Six, when clicking on a historical record item, fill the content into EditText and automatically execute the search function.

The UI diagram is as follows:

=============== UI Implementation ===============

Search Header:

The overall layout is a horizontal LinearLayout, sequentially placing ImageView(return arrow), EditText(search box), TextView(cancel).

The layout file is as follows:

<LinearLayout
  android:layout_width="match_parent"
  android:layout_height=""45dp"
  android:background="@color"/black_14141f"
  android:gravity="center_vertical"
  android:orientation="horizontal"
  android:paddingBottom="6dp"
  android:paddingTop="6dp">
  <ImageView
    android:id="@"+id/iv_back"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:paddingLeft="14dp"
    android:paddingRight="14dp"
    android:src="@drawable/icon_back" />
  <RelativeLayout
    android:id="@"+id/rl_search_layout"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="wrap_content"
    android:background="@drawable"/shape_search_bj"
    android:gravity="center"
    android:orientation="horizontal">
    <!--      <ImageView
            android:layout_centerVertical="true"
            android:layout_alignParentLeft="true"
            android:id="@"+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop=""5dp"
            android:layout_marginBottom=""5dp"
            android:background="@drawable"/icon_black_search" />-->
    <EditText
      android:id="@"+id/et_search"
      android:layout_width="match_parent"
      android:layout_height=""30dp"
      android:layout_centerVertical="true"
      android:layout_marginLeft=""10dp"
      android:layout_toLeftOf="@"+id/close"
      android:layout_toRightOf="@"+id/tv"
      android:background="@null"
      android:hint="Enter product name or store name"
      android:imeOptions="actionSearch"
      android:singleLine="true"
      android:textColor="@color"/black3"
      android:textColorHint="@color/gray_aaaaaa"
      android:textSize=""14sp" />
    <!--      <ImageView
            android:layout_centerVertical="true"
            android:layout_alignParentRight="true"
            android:id="@"+id/clear_search"
            android:background="@drawable"/icon_dialog_close"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textColor="@color"/white"
            android:textSize=""18sp" />-->
  </RelativeLayout>
  <TextView
    android:id="@"+id/tv_cancel"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft=""14dp"
    android:layout_marginRight=""14dp"
    android:gravity="center_vertical"
    android:text="取消"
    android:textColor="@color"/white"
    android:textSize=""18sp" />
</LinearLayout>

Directly copied from the project, please replace the resource files as needed.

==================================

Historical record layout:

Encapsulate TextView (historical search for four characters) with a vertical layout LinearLayout +ListView (search records)+ListView's footer view (clear historical records) implementation.

(The Taobao version should be a ListView instead, the historical search text can be implemented using ListView's HeaderView. In our company's product design, the length of the separator below the historical search text is different from the length of the item separator of the historical records, so I used TextView directly, with the same difference)

The code is as follows:

<LinearLayout
  android:id="@"+id/ll_search_history"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical">
  <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom=""14dp"
    android:layout_marginLeft=""14dp"
    android:layout_marginTop=""14dp"
    android:drawableLeft="@drawable"/brand_search_history_icon"
    android:drawablePadding=""10dp"
    android:text="历史记录"
    android:textColor="#"333333" />
  <View
    android:layout_width="match_parent"
    android:layout_height=""1dp"
    android:background="@color"/bg_dfdfdf" />
  <ListView
    android:id="@"+id/listView"
    android:divider="@null"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"></ListView>
</LinearLayout>

The dividing line is usually implemented using View's background setting to save the UI slicing.

================Function Implementation Part==================

First, click the EditText to pop up the soft keyboard input method, and the right bottom key is labeled [Search].

You only need to configure the following attribute in the EditText control's xml file:

android:imeOptions="actionSearch"

=================================

Second, listen to the soft keyboard input method's Search event.

Set the OnEditorActionListener event for the EditText control, and judge when actionId == EditorInfo.IME_ACTION_SEARCH, it means the Search button is pressed.

The code is as follows, here we hide the soft keyboard and execute the search request.

mEditTextSearch.setOnEditorActionListener(new TextView.OnEditorActionListener() {
  @Override
  public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    if (actionId == EditorInfo.IME_ACTION_SEARCH) {
      // First, hide the keyboard
      ((InputMethodManager) mEditTextSearch.getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
          .hideSoftInputFromWindow(MainActivity.this.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
      //The specific search logic
      //The search request is transferred to the function to handle:
      //search(String.valueOf(mEditTextSearch.getText()));
      Toast.makeText(MainActivity.this,"Pressed the search button",Toast.LENGTH_SHORT).show();
      return true;
    }
    return false;
  }
});

==================================

Third, after entering content in the EditText,1000ms without modification will automatically trigger the search function.

This is to listen to the EditText's TextChangedListener, in the afterTextChanged(Editable s) callback method, first judge whether the current string in the EditText is null/is empty, if it is empty, then do not search, display the history records, if not empty, then use the Handler.sendMessageDelayed method to delay500ms/1000ms to execute the search function.

There is a situation here that is, if the user in1000ms input new content, delete or modify characters, and the new content should be delayed1000ms search, or the character is deleted and becomes empty, then it should not be searched anymore, that is, the user has10Within 00ms of change, the logic judgment should be made based on the latest situation. Therefore, a statement is added before the judgment, and before each execution of the afterTextChanged() method, it is first judged whether there are any pending search requests in mHandler, and if there are, the request Message is removed first.

The code is as follows: The introduction of Handler is mainly to achieve delayed automatic search and facilitate the cancellation of search requests.

      

 mEditTextSearch.addTextChangedListener(new TextWatcher() {
      @Override
      public void beforeTextChanged(CharSequence s, int start, int count, int after) {
      }
      @Override
      public void onTextChanged(CharSequence s, int start, int before, int count) {
      }
      @Override
      public void afterTextChanged(Editable s) {
//The text has changed, there are undelivered search requests, they should be canceled
        if(mHandler.hasMessages(MSG_SEARCH)){
          mHandler.removeMessages(MSG_SEARCH);
        }
        //If it is empty, directly display the search history
        if(TextUtils.isEmpty(s)){
          //showHistory();
        }else {//otherwise delay5
          mHandler.sendEmptyMessageDelayed(MSG_SEARCH,500); //Automatic search feature Delete
        }
      }
    });
  }
  private Handler mHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
      //Search request
      Toast.makeText(MainActivity.this,"Searching......",Toast.LENGTH_SHORT).show();
      //search(String.valueOf(mEditTextSearch.getText()));
    }
  };
  private static final int MSG_SEARCH = 1;

==================================

Four, historical records sorted by time,

In the encapsulated search method, I do two things, one is to send a "search keyword" request to the server. Two, synchronize the keyword to SharedPrefrences (abbreviated as SP) as a historical record.

Skip the first, let's talk about the second here.

The keyword is saved to SP in the form of key-The form of value, here I take the time in the form of yyyyMMddHHmmss as the key, (for subsequent sorting), and store the keyword as the value.

Since the historical records need to have a sense of update (the latest in front, for example, jeans (originally the second), will update the time of this record, and when you look at it next time, the arrangement of jeans will be in the first place.), so before saving the keyword each time, I first query from SP (return all data in SP), traverse this returned map to see if there is a record with the value, if there is, then delete it, because a record with the same value will be inserted later, so this kind of deletion+The operation of insertion has realized the update function of SP update (the original SP only had the methods of add and delete).

The code to save historical records is as follows:

private SimpleDateFormat mFormat;
mFormat = new SimpleDateFormat("yyyyMMddHHmmss");
/**
 * Save the historical records to sp, key=current time(20160302133455Convenient for sorting) ,value=keyword
 * @param keyWords
 */
private void saveSearchHistory(String keyWords) {
  //Before saving, you need to query whether there is a record with the same value in SP, and if there is, delete it. This ensures that the search history will not have duplicate entries
  Map<String, String> historys = (Map<String, String>) SearchHistoryUtils.getAll(getActivity());
  for (Map.Entry<String, String> entry : historys.entrySet()) {
    if(keyWords.equals(entry.getValue())){
      SearchHistoryUtils.remove(getActivity(), entry.getKey());
    }
  }
  SearchHistoryUtils.put(getActivity(), "", + mFormat.format(new Date()), keyWords);
}

Query historical records

When querying, it first returns all the data in SP, returning a MAP collection. Then take out the keySet of the MAP collection. Since the keys are inserted in a specified numeric format, they are sorted. This is an ascending order sort.

First calculate the length of the keySet collection keyLeng, that is, how many historical records there are,

Then compare it with the maximum number of items to be displayed (HISTORY_MAX, which is defined by our product here,5items for comparison,

If the number of historical records is greater than the number of records to be displayed, then take it as HISTORY_MAX,5items. Otherwise, use the number of historical records keyLeng.

Then loop to take out the last n data from the returned Map collection, sorted by key in ascending order, and add them to the collection for display. Here, we need to check if the size of the data collection to be displayed is not equal to 0, indicating that there are historical records, then display footerview(clear history records), otherwise do not display.

The code is as follows:

/**
 * The maximum number of historical records to display
 */
private static final int HISTORY_MAX = 5;
/**
 * Query search history from SP and display it according to time.
 */
private void showSearchHistory() {
  Map<String, String> hisAll = (Map<String, String>) SearchHistoryUtils.getAll(getActivity());
  //Sort the key in ascending order
  Object[] keys = hisAll.keySet().toArray();
  Arrays.sort(keys);
  int keyLeng = keys.length;
  //Here, if the number of historical records is greater than the maximum number that can be displayed, use the maximum number as the loop condition to prevent the number of historical records from exceeding the maximum.-The maximum number is negative, array out of bounds
  int hisLeng = keyLeng > HISTORY_MAX &63; HISTORY_MAX : keyLeng;
  for (int i = 1; i <= hisLeng; i++) {
    mResults.add(hisAll.get(keys[keyLeng - i]);
  }
  mAdapter.setDatas(mResults);
  //If the size is not 0, display the footerview
  mFooterView.setVisibility(0 != mResults.size() &63; View.VISIBLE: View.GONE);
}

================================

Five, clear historical records

This function is very simple, just clear the data in SP, but you need to update the visibility of ListView and footerView synchronously.

/**
 * Clear historical records
 */
private void clearsearchHistory() {
  SearchHistoryUtils.clear(this);
  //Refresh the historical record display page at the same time
  //mResults = new ArrayList<>();
  //mAdapter.setDatas(mResults);
  //If the size is not 0, display the footerview
  //mFooterView.setVisibility(0 != mResults.size() &63; View.VISIBLE: View.GONE);
}

==================================

Six, when clicking on a historical record item, fill the content into EditText and automatically execute the search function.

Nothing much to say, set click listener for ListView, and then set the content for setText() of EditText, execute search operation synchronously. Omit.


==================================

Optimization 7: To prevent the number of historical records from increasing over time, I call the method to delete extra historical records in the onDestroy() method of this Fragment, keeping the number of elements in SP within the maximum number to be displayed:

Logic: If the number of map entries exceeds the maximum to be displayed, it means that extra entries need to be deleted.

Then, just like the logic for displaying historical records in the query, sort the keys first, delete, and remove the few with smaller key values.

The code is as follows:

/**
 * Delete the extra historical records
 * If the number of historical records is greater than the specified quantity,10If the number of records exceeds the specified number, then sort them by key in ascending order and delete the first few.
 */
private void delMoreSearchHistory() {}}
  Map<String, String> hisAll = (Map<String, String>) SearchHistoryUtils.getAll(this);
  if (hisAll.size() > HISTORY_MAX) {
    //Sort the key in ascending order
    Object[] keys = hisAll.keySet().toArray();
    Arrays.sort(keys);
    // LENGTH = 12 , MAX = 10 , I = 1,0,count =2;
    for (int i = keys.length - HISTORY_MAX - 1; i > -1; i--) {
      SearchHistoryUtils.remove(this, (String) keys[i]);
    }
  }
}

================================================

This is the first small feature I did in the new company, so I record it here.

In fact, the idea of this feature is roughly as described above, but there are some things to be manually processed with SP, so there are some things to say.

If historical records are saved in a database, it is more simple to use the select query condition to sort the time and delete with conditions.

That's all for this article, I hope it will be helpful to everyone's learning, and I also hope everyone will support the Yell Tutorial.

Statement: The content of this article is from the Internet, the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously, and this website does not own the copyright. It has not been manually edited and does not assume relevant legal liabilities. If you find any copyright-infringing content, please send an email to: notice#w3Please replace '#' with '@' when sending an email to report infringement, and provide relevant evidence. Once verified, this site will immediately delete the infringing content.

You May Also Like