Android:自定义View

一、简介

点击查看创建自定义视图组件中文官网

Android 提供了一个复杂而强大的组件化模型,用于基于基本布局类 View 和 ViewGroup 构建界面。该平台包含各种预构建的 View 和 ViewGroup 子类(分别称为 widget 和布局),可供您用来构建界面。

可用布局包括ConstraintLayout、 LinearLayout、FrameLayout、RelativeLayout 等。

如果预构建的 widget 或布局都不能满足您的需求,您可以创建自己的 View 子类。如果您只需要对现有 widget 或布局进行细微调整,则可以创建相应 widget 或布局的子类并替换其方法。

通过创建自己的 View 子类,您可以精确控制屏幕元素的外观和功能。为了让您了解自定义视图可以实现哪些控制,下面列举了一些示例来说明您可以如何使用自定义视图:

  • 您可以创建一个完全自定义渲染的 View 类型,例如,使用 2D 图形渲染的“音量控制”旋钮,类似于模拟电子控件。
  • 您可以将一组 View 组件组合成一个新的组件,例如制作组合框(弹出式列表和自由输入文本字段的组合)、双窗格选择器控件(左右窗格,其中每个窗格都有一个列表,您可以在其中重新分配哪个列表中的项)等。
  • 您可以替换 EditText 组件在屏幕上的渲染方式。 NotePad 示例应用使用此效果有效地创建了一个带线条的记事本页面。
  • 您可以捕获其他事件(例如按键),并以自定义方式(例如在游戏中)处理这些事件。

二、基本方法

下面简要介绍了创建您自己的 View 组件需要了解的内容:

  1. 使用您自己的类扩展现有的 View 类或子类。
  2. 替换父类中的某些方法。要替换的父类方法以 on 开头,例如onLayout() 、onMeasure() 、 onDraw() 和 onKeyDown()。
  3. 使用您的新扩展类。完成后,您可以使用新的扩展类来代替其所基于的视图。

三、扩展 onDraw() 和 onMeasure()

onDraw() 方法提供了一个 Canvas,您可以在其上实现所需的任何内容:2D 图形、其他标准或自定义组件、样式文本或您能想到的任何其他内容。

注意:这不适用于 3D 图形。如果要使用 3D 图形,请扩展 SurfaceView(而非 View)并从单独的线程绘制。如需了解详情,请参阅 GLSurfaceViewActivity 示例。

onMeasure() 涉及更多。onMeasure() 是组件与其容器之间渲染协定的关键部分。必须替换 onMeasure(),才能高效且准确地报告其所含部分的测量结果。父级的限制要求(传递到 onMeasure() 方法)以及计算后使用测量的宽度和高度调用 setMeasuredDimension() 方法的要求,让这变得稍微有些复杂。如果您不通过已替换的 onMeasure() 方法调用此方法,则会导致测量时出现异常。

概括来讲,实现 onMeasure() 如下所示:

  • 系统会使用宽度和高度规范调用替换的 onMeasure() 方法,这些规范被视为对您生成的宽度和高度的限制要求。widthMeasureSpec 和 heightMeasureSpec 参数都是表示维度的整数代码。有关这些规范可能要求的限制的完整参考,请参阅 View.onMeasure(int, int) 下的参考文档。此参考文档还介绍了整个测量操作。
  • 组件的 onMeasure() 方法会计算渲染组件所需的测量宽度和高度。它必须尽量符合传入的规范,但也可能会超过这些规范。在这种情况下,父级可以选择要执行的操作,包括裁剪、滚动、抛出异常或要求 onMeasure() 重试,或许使用不同的测量规范。
  • 计算宽度和高度后,使用计算出的测量值调用 setMeasuredDimension(int width, int height) 方法。否则会导致异常。

四、自定义View的绘制流程

4.1 创建
  1. 构造函数
    构造函数有两种形式:从代码创建视图时调用,另一种形式在从布局文件膨胀视图时调用。第二种形式解析并应用布局文件中定义的属性。
  2. onFinishInflate()
    在视图及其所有子项都从 XML 扩充之后调用。
4.2 布局
  1. onMeasure(int, int)
    调用以确定此视图及其所有子级的大小要求。
  2. onLayout(boolean, int, int, int, int)
    在此视图必须为其所有子视图分配大小和位置时调用。
  3. onSizeChanged(int, int, int, int)
    在此视图的大小发生更改时调用。
4.3 绘制
  1. onDraw(Canvas)
    在视图必须渲染其内容时调用。
4.4 事件处理
  1. onKeyDown(int, KeyEvent)
    在发生按键按下事件时调用。
  2. onKeyUp(int, KeyEvent)
    在发生 key up 事件时调用
  3. onTrackballEvent(MotionEvent)
    在发生轨迹球动作事件时调用。
  4. onTouchEvent(MotionEvent)
    在发生触摸屏动作事件时调用。
4.5 侧重点
  1. onFocusChanged(boolean, int, Rect)
    在视图获得或失去焦点时调用。
  2. onWindowFocusChanged(boolean)
    在包含视图的窗口获得或失去焦点时调用。
4.6 附加
  1. onAttachedToWindow()
    在视图附加到窗口时调用。
  2. onDetachedFromWindow()
    在视图与其窗口分离时调用。
  3. onWindowVisibilityChanged(int)
    在包含视图的窗口的可见性发生更改时调用。

五、复合控件

如果您不想创建完全自定义的组件,而是希望将可重复使用的组件(由一组现有控件组成)组合在一起,那么创建复合组件(或复合控件)可能是最好的选择。总而言之,这会将许多原子性控件或视图整合到可视为一项的逻辑项组中。 例如,组合框可以是单行 EditText 字段和附有弹出式列表的相邻按钮的组合。如果用户点按该按钮并从列表中选择了内容,系统会填充 EditText 字段,但用户也可以根据需要直接在 EditText 中输入内容。

在 Android 中,还有另外两个视图可用于执行此操作:Spinner 和 AutoCompleteTextView。无论如何,这个组合框概念都是一个很好的例子。

如需创建复合组件,请执行以下操作:

  • 与 Activity 一样,使用声明式(基于 XML)方法创建包含的组件,或者以程序化方式从代码中嵌套组件。通常的起点是某种类型的 Layout,因此请创建一个扩展 Layout 的类。对于组合框,您可以使用水平方向的 LinearLayout。您可以在里面嵌套其他布局,使复合组件可以任意复杂化和结构化。
  • 在新类的构造函数中,获取父类所需的任何参数,并先将它们传递给父类构造函数。然后,您可以设置其他视图,以便在新组件中使用。您可以在这里创建 EditText 字段和弹出式列表。您可以在 XML 中引入您自己的属性和参数,以便构造函数可以提取和使用。
  • (可选)为包含的视图可能生成的事件创建监听器。例如,在选择了列表的情况下,列表项点击监听器会更新 EditText 的内容。
  • (可选)使用访问器和修饰符创建自己的属性。例如,最初在组件中设置 EditText 值,并在需要时查询其内容。
  • (可选)替换 onDraw() 和 onMeasure()。在扩展 Layout 时通常没有必要这样做,因为布局具有可能正常运行的默认行为。
  • (可选)替换其他 on 方法(如 onKeyDown()),例如,在点按某个键时,从组合框的弹出式列表中选择特定默认值。

使用 Layout 作为自定义控件的基础具有如下优势:

  • 您可以使用声明式 XML 文件指定布局(就像使用 activity 屏幕一样),也可以以编程方式创建视图并将其从代码嵌套到布局中。
  • onDraw() 和 onMeasure() 方法以及大多数其他 on 方法具有合适的行为,因此您无需替换它们。
  • 您可以快速构建任意复杂的复合视图,并像使用单个组件一样重复使用它们。

六、修改现有视图类型

如果存在与您所需的组件类似的组件,您可以扩展该组件并替换您想要更改的行为。您可以使用完全自定义的组件执行所有操作,但通过从 View 层次结构中更专用的类着手,您可以免费获得一些执行您所需要的行为。

例如,NotePad 示例应用演示了使用 Android 平台的多个方面。其中包括扩展 EditText 视图,使之成为带线条的记事本。这并不是一个完美的示例,并且用于执行此操作的 API 可能会发生变化,但它演示了相关原则。

如果您尚未执行此操作,请将 NotePad 示例导入 Android Studio,或使用提供的链接查看源代码。请特别留意 NoteEditor.java 文件中 LinedEditText 的定义。

下面是此文件中的一些注意事项:

  1. 定义
    该类使用以下行进行定义:
    public static class LinedEditText extends EditText
    LinedEditText 定义为 NoteEditor Activity 中的一个内部类,但它是公共类,因此可以作为 NoteEditor.LinedEditText 从 NoteEditor 类外部进行访问。
    此外,LinedEditText 为 static,这意味着它不会生成允许其访问父类数据的所谓“合成方法”。这意味着它的行为表现为一个单独的类,而不是与 NoteEditor 密切相关的类。如果内部类不需要从外部类访问状态,则这是一种更简洁的方法来创建内部类。它使生成的类保持较小,并便于其他类使用。
    LinedEditText 扩展了 EditText(在本例中是要自定义的视图)。完成后,新类可以代替普通的 EditText 视图。
  2. 类初始化
    与往常一样,首先调用父类。这不是默认构造函数,而是参数化构造函数。EditText 在从 XML 布局文件膨胀时是使用这些参数创建的。因此,构造函数需要获取这些方法并将其传递给父类构造函数。
  3. 替换的方法
    此示例仅替换 onDraw() 方法,但您可能需要在创建自己的自定义组件时替换其他方法。

    在此示例中,通过替换 onDraw() 方法,您可以在 EditText 视图画布上绘制蓝色线条。系统会将画布传入被替换的 onDraw() 方法。系统会在 super.onDraw() 方法结束之前调用该方法。必须调用父类方法。在本例中,请在绘制要包含的行后调用该函数。
  4. 自定义组件
    现在,您已经有了自定义组件,但如何使用它呢?在记事本示例中,自定义组件直接从声明式布局中使用,因此请查看 res/layout 文件夹中的 note_editor.xml:
<view xmlns:android="http://schemas.android.com/apk/res/android"
    class="com.example.android.notepad.NoteEditor$LinedEditText"
    android:id="@+id/note"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent"
    android:padding="5dp"
    android:scrollbars="vertical"
    android:fadingEdge="vertical"
    android:gravity="top"
    android:textSize="22sp"
    android:capitalize="sentences"
/>

将自定义组件创建为 XML 中的通用视图,并使用完整软件包指定类。您定义的内部类使用 NoteEditor$LinedEditText 表示法引用,这是以 Java 编程语言引用内部类的标准方式。

如果您的自定义视图组件未定义为内部类,您可以使用 XML 元素名称声明视图组件,并排除 class 属性。例如:

<com.example.android.notepad.LinedEditText
  id="@+id/note"
  ... />

请注意,LinedEditText 类现在是一个单独的类文件。当该类嵌套在 NoteEditor 类中时,此方法不起作用。

定义中的其他属性和参数是传入自定义组件构造函数中和再传递到 EditText 构造函数的,因此它们与您用于 EditText 视图的参数相同。您也可以添加自己的参数。

七、效果图

在这里插入图片描述

在这里插入图片描述

八、NoteEditor代码

/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.example.android.notepad;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.EditText;
import com.example.android.notepad.NotePad.Notes;
/**
 * This Activity handles "editing" a note, where editing is responding to
 * {@link Intent#ACTION_VIEW} (request to view data), edit a note
 * {@link Intent#ACTION_EDIT}, create a note {@link Intent#ACTION_INSERT}, or
 * create a new note from the current contents of the clipboard {@link Intent#ACTION_PASTE}.
 */
public class NoteEditor extends Activity implements LoaderManager.LoaderCallbacks<Cursor> {
    // For logging and debugging purposes
    private static final String TAG = "NoteEditor";
    /*
     * Creates a projection that returns the note ID and the note contents.
     */
    private static final String[] PROJECTION =
        new String[] {
            NotePad.Notes._ID,
            NotePad.Notes.COLUMN_NAME_TITLE,
            NotePad.Notes.COLUMN_NAME_NOTE
    };
    // A label for the saved state of the activity
    private static final String ORIGINAL_CONTENT = "origContent";
    // This Activity can be started by more than one action. Each action is represented
    // as a "state" constant
    private static final int STATE_EDIT = 0;
    private static final int STATE_INSERT = 1;
    private static final int LOADER_ID = 1;
    // Global mutable variables
    private int mState;
    private Uri mUri;
    private EditText mText;
    private String mOriginalContent;
    /**
     * Defines a custom EditText View that draws lines between each line of text that is displayed.
     */
    public static class LinedEditText extends EditText {
        private Rect mRect;
        private Paint mPaint;
        // This constructor is used by LayoutInflater
        public LinedEditText(Context context, AttributeSet attrs) {
            super(context, attrs);
            // Creates a Rect and a Paint object, and sets the style and color of the Paint object.
            mRect = new Rect();
            mPaint = new Paint();
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(0x800000FF);
        }
        /**
         * This is called to draw the LinedEditText object
         * @param canvas The canvas on which the background is drawn.
         */
        @Override
        protected void onDraw(Canvas canvas) {
            // Gets the number of lines of text in the View.
            int count = getLineCount();
            // Gets the global Rect and Paint objects
            Rect r = mRect;
            Paint paint = mPaint;
            /*
             * Draws one line in the rectangle for every line of text in the EditText
             */
            for (int i = 0; i < count; i++) {
                // Gets the baseline coordinates for the current line of text
                int baseline = getLineBounds(i, r);
                /*
                 * Draws a line in the background from the left of the rectangle to the right,
                 * at a vertical position one dip below the baseline, using the "paint" object
                 * for details.
                 */
                canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);
            }
            // Finishes up by calling the parent method
            super.onDraw(canvas);
        }
    }
    /**
     * This method is called by Android when the Activity is first started. From the incoming
     * Intent, it determines what kind of editing is desired, and then does it.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Recovering the instance state from a previously destroyed Activity instance
        if (savedInstanceState != null) {
            mOriginalContent = savedInstanceState.getString(ORIGINAL_CONTENT);
        }
        /*
         * Creates an Intent to use when the Activity object's result is sent back to the
         * caller.
         */
        final Intent intent = getIntent();
        /*
         *  Sets up for the edit, based on the action specified for the incoming Intent.
         */
        // Gets the action that triggered the intent filter for this Activity
        final String action = intent.getAction();
        // For an edit action:
        if (Intent.ACTION_EDIT.equals(action)) {
            // Sets the Activity state to EDIT, and gets the URI for the data to be edited.
            mState = STATE_EDIT;
            mUri = intent.getData();
            // For an insert or paste action:
        } else if (Intent.ACTION_INSERT.equals(action)
                || Intent.ACTION_PASTE.equals(action)) {
            // Sets the Activity state to INSERT, gets the general note URI, and inserts an
            // empty record in the provider
            mState = STATE_INSERT;
            setTitle(getText(R.string.title_create));
            mUri = getContentResolver().insert(intent.getData(), null);
            /*
             * If the attempt to insert the new note fails, shuts down this Activity. The
             * originating Activity receives back RESULT_CANCELED if it requested a result.
             * Logs that the insert failed.
             */
            if (mUri == null) {
                // Writes the log identifier, a message, and the URI that failed.
                Log.e(TAG, "Failed to insert new note into " + getIntent().getData());
                // Closes the activity.
                finish();
                return;
            }
            // Since the new entry was created, this sets the result to be returned
            // set the result to be returned.
            setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));
        // If the action was other than EDIT or INSERT:
        } else {
            // Logs an error that the action was not understood, finishes the Activity, and
            // returns RESULT_CANCELED to an originating Activity.
            Log.e(TAG, "Unknown action, exiting");
            finish();
            return;
        }
        // Initialize the LoaderManager and start the query
        getLoaderManager().initLoader(LOADER_ID, null, this);
        // For a paste, initializes the data from clipboard.
        if (Intent.ACTION_PASTE.equals(action)) {
            // Does the paste
            performPaste();
            // Switches the state to EDIT so the title can be modified.
            mState = STATE_EDIT;
        }
        // Sets the layout for this Activity. See res/layout/note_editor.xml
        setContentView(R.layout.note_editor);
        // Gets a handle to the EditText in the the layout.
        mText = (EditText) findViewById(R.id.note);
    }
    /**
     * This method is called when an Activity loses focus during its normal operation.
     * The Activity has a chance to save its state so that the system can restore
     * it.
     *
     * Notice that this method isn't a normal part of the Activity lifecycle. It won't be called
     * if the user simply navigates away from the Activity.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        // Save away the original text, so we still have it if the activity
        // needs to be re-created.
        outState.putString(ORIGINAL_CONTENT, mOriginalContent);
        // Call the superclass to save the any view hierarchy state
        super.onSaveInstanceState(outState);
    }
    /**
     * This method is called when the Activity loses focus.
     *
     * While there is no need to override this method in this app, it is shown here to highlight
     * that we are not saving any state in onPause, but have moved app state saving to onStop
     * callback.
     * In earlier versions of this app and popular literature it had been shown that onPause is good
     * place to persist any unsaved work, however, this is not really a good practice because of how
     * application and process lifecycle behave.
     * As a general guideline apps should have a way of saving their business logic that does not
     * solely rely on Activity (or other component) lifecyle state transitions.
     * As a backstop you should save any app state, not saved during lifetime of the Activity, in
     * onStop().
     * For a more detailed explanation of this recommendation please read
     * <a href = "https://developer.android.com/guide/topics/processes/process-lifecycle.html">
     * Processes and Application Life Cycle </a>.
     * <a href="https://developer.android.com/training/basics/activity-lifecycle/pausing.html">
     * Pausing and Resuming an Activity </a>.
     */
    @Override
    protected void onPause() {
        super.onPause();
    }
    /**
     * This method is called when the Activity becomes invisible.
     *
     * For Activity objects that edit information, onStop() may be the one place where changes maybe
     * saved.
     *
     * If the user hasn't done anything, then this deletes or clears out the note, otherwise it
     * writes the user's work to the provider.
     */
    @Override
    protected void onStop() {
        super.onStop();
        // Get the current note text.
        String text = mText.getText().toString();
        int length = text.length();
            /*
             * If the Activity is in the midst of finishing and there is no text in the current
             * note, returns a result of CANCELED to the caller, and deletes the note. This is done
             * even if the note was being edited, the assumption being that the user wanted to
             * "clear out" (delete) the note.
             */
        if (isFinishing() && (length == 0)) {
            setResult(RESULT_CANCELED);
            deleteNote();
                /*
                 * Writes the edits to the provider. The note has been edited if an existing note
                 * was retrieved into the editor *or* if a new note was inserted.
                 * In the latter case, onCreate() inserted a new empty note into the provider,
                 * and it is this new note that is being edited.
                 */
        } else if (mState == STATE_EDIT) {
            // Creates a map to contain the new values for the columns
            updateNote(text, null);
        } else if (mState == STATE_INSERT) {
            updateNote(text, text);
            mState = STATE_EDIT;
        }
    }
    /**
     * This method is called when the user clicks the device's Menu button the first time for
     * this Activity. Android passes in a Menu object that is populated with items.
     *
     * Builds the menus for editing and inserting, and adds in alternative actions that
     * registered themselves to handle the MIME types for this application.
     *
     * @param menu A Menu object to which items should be added.
     * @return True to display the menu.
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate menu from XML resource
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.editor_options_menu, menu);
        // Only add extra menu items for a saved note 
        if (mState == STATE_EDIT) {
            // Append to the
            // menu items for any other activities that can do stuff with it
            // as well.  This does a query on the system for any activities that
            // implement the ALTERNATIVE_ACTION for our data, adding a menu item
            // for each one that is found.
            Intent intent = new Intent(null, mUri);
            intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
            menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
                    new ComponentName(this, NoteEditor.class), null, intent, 0, null);
        }
        return super.onCreateOptionsMenu(menu);
    }
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        // Check if note has changed and enable/disable the revert option
        Cursor cursor = getContentResolver().query(
            mUri,        // The URI for the note that is to be retrieved.
            PROJECTION,  // The columns to retrieve
            null,        // No selection criteria are used, so no where columns are needed.
            null,        // No where columns are used, so no where values are needed.
            null         // No sort order is needed.
        );
        cursor.moveToFirst();
        int colNoteIndex = cursor.getColumnIndex(Notes.COLUMN_NAME_NOTE);
        String savedNote = cursor.getString(colNoteIndex);
        String currentNote = mText.getText().toString();
        if (savedNote.equals(currentNote)) {
            menu.findItem(R.id.menu_revert).setVisible(false);
        } else {
            menu.findItem(R.id.menu_revert).setVisible(true);
        }
        return super.onPrepareOptionsMenu(menu);
    }
    /**
     * This method is called when a menu item is selected. Android passes in the selected item.
     * The switch statement in this method calls the appropriate method to perform the action the
     * user chose.
     *
     * @param item The selected MenuItem
     * @return True to indicate that the item was processed, and no further work is necessary. False
     * to proceed to further processing as indicated in the MenuItem object.
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle all of the possible menu actions.
        switch (item.getItemId()) {
        case R.id.menu_save:
            String text = mText.getText().toString();
            updateNote(text, null);
            finish();
            break;
        case R.id.menu_delete:
            deleteNote();
            finish();
            break;
        case R.id.menu_revert:
            cancelNote();
            break;
        }
        return super.onOptionsItemSelected(item);
    }
//BEGIN_INCLUDE(paste)
    /**
     * A helper method that replaces the note's data with the contents of the clipboard.
     */
    private final void performPaste() {
        // Gets a handle to the Clipboard Manager
        ClipboardManager clipboard = (ClipboardManager)
                getSystemService(Context.CLIPBOARD_SERVICE);
        // Gets a content resolver instance
        ContentResolver cr = getContentResolver();
        // Gets the clipboard data from the clipboard
        ClipData clip = clipboard.getPrimaryClip();
        if (clip != null) {
            String text=null;
            String title=null;
            // Gets the first item from the clipboard data
            ClipData.Item item = clip.getItemAt(0);
            // Tries to get the item's contents as a URI pointing to a note
            Uri uri = item.getUri();
            // Tests to see that the item actually is an URI, and that the URI
            // is a content URI pointing to a provider whose MIME type is the same
            // as the MIME type supported by the Note pad provider.
            if (uri != null && NotePad.Notes.CONTENT_ITEM_TYPE.equals(cr.getType(uri))) {
                // The clipboard holds a reference to data with a note MIME type. This copies it.
                Cursor orig = cr.query(
                        uri,            // URI for the content provider
                        PROJECTION,     // Get the columns referred to in the projection
                        null,           // No selection variables
                        null,           // No selection variables, so no criteria are needed
                        null            // Use the default sort order
                );
                // If the Cursor is not null, and it contains at least one record
                // (moveToFirst() returns true), then this gets the note data from it.
                if (orig != null) {
                    if (orig.moveToFirst()) {
                        int colNoteIndex = orig.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE);
                        int colTitleIndex = orig.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE);
                        text = orig.getString(colNoteIndex);
                        title = orig.getString(colTitleIndex);
                    }
                    // Closes the cursor.
                    orig.close();
                }
            }
            // If the contents of the clipboard wasn't a reference to a note, then
            // this converts whatever it is to text.
            if (text == null) {
                text = item.coerceToText(this).toString();
            }
            // Updates the current note with the retrieved title and text.
            updateNote(text, title);
        }
    }
//END_INCLUDE(paste)
    /**
     * Replaces the current note contents with the text and title provided as arguments.
     * @param text The new note contents to use.
     * @param title The new note title to use
     */
    private final void updateNote(String text, String title) {
        // Sets up a map to contain values to be updated in the provider.
        ContentValues values = new ContentValues();
        values.put(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE, System.currentTimeMillis());
        // If the action is to insert a new note, this creates an initial title for it.
        if (mState == STATE_INSERT) {
            // If no title was provided as an argument, create one from the note text.
            if (title == null) {
  
                // Get the note's length
                int length = text.length();
                // Sets the title by getting a substring of the text that is 31 characters long
                // or the number of characters in the note plus one, whichever is smaller.
                title = text.substring(0, Math.min(30, length));
  
                // If the resulting length is more than 30 characters, chops off any
                // trailing spaces
                if (length > 30) {
                    int lastSpace = title.lastIndexOf(' ');
                    if (lastSpace > 0) {
                        title = title.substring(0, lastSpace);
                    }
                }
            }
            // In the values map, sets the value of the title
            values.put(NotePad.Notes.COLUMN_NAME_TITLE, title);
        } else if (title != null) {
            // In the values map, sets the value of the title
            values.put(NotePad.Notes.COLUMN_NAME_TITLE, title);
        }
        // This puts the desired notes text into the map.
        values.put(NotePad.Notes.COLUMN_NAME_NOTE, text);
        /*
         * Updates the provider with the new values in the map. The ListView is updated
         * automatically. The provider sets this up by setting the notification URI for
         * query Cursor objects to the incoming URI. The content resolver is thus
         * automatically notified when the Cursor for the URI changes, and the UI is
         * updated.
         * Note: This is being done on the UI thread. It will block the thread until the
         * update completes. In a sample app, going against a simple provider based on a
         * local database, the block will be momentary, but in a real app you should use
         * android.content.AsyncQueryHandler or android.os.AsyncTask.
         */
        getContentResolver().update(
            mUri,    // The URI for the record to update.
            values,  // The map of column names and new values to apply to them.
            null,    // No selection criteria are used, so no where columns are necessary.
            null     // No where columns are used, so no where arguments are necessary.
        );
    }
    /**
     * This helper method cancels the work done on a note.  It deletes the note if it was
     * newly created, or reverts to the original text of the note i
     */
    private final void cancelNote() {
        if (mState == STATE_EDIT) {
            // Put the original note text back into the database
            ContentValues values = new ContentValues();
            values.put(NotePad.Notes.COLUMN_NAME_NOTE, mOriginalContent);
            getContentResolver().update(mUri, values, null, null);
        } else if (mState == STATE_INSERT) {
            // We inserted an empty note, make sure to delete it
            deleteNote();
        }
        setResult(RESULT_CANCELED);
        finish();
    }
    /**
     * Take care of deleting a note.  Simply deletes the entry.
     */
    private final void deleteNote() {
        getContentResolver().delete(mUri, null, null);
        mText.setText("");
    }
    // LoaderManager callbacks
    @Override
    public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
        return new CursorLoader(
            this,
            mUri,        // The URI for the note that is to be retrieved.
            PROJECTION,  // The columns to retrieve
            null,        // No selection criteria are used, so no where columns are needed.
            null,        // No where columns are used, so no where values are needed.
            null         // No sort order is needed.
        );
    }
    @Override
    public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
        // Modifies the window title for the Activity according to the current Activity state.
        if (cursor != null && cursor.moveToFirst() && mState == STATE_EDIT) {
            // Set the title of the Activity to include the note title
            int colTitleIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE);
            int colNoteIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE);
            // Gets the title and sets it
            String title = cursor.getString(colTitleIndex);
            Resources res = getResources();
            String text = String.format(res.getString(R.string.title_edit), title);
            setTitle(text);
            // Gets the note text from the Cursor and puts it in the TextView, but doesn't change
            // the text cursor's position.
            String note = cursor.getString(colNoteIndex);
            mText.setTextKeepState(note);
            // Stores the original note text, to allow the user to revert changes.
            if (mOriginalContent == null) {
                mOriginalContent = note;
            }
        }
    }
    @Override
    public void onLoaderReset(Loader<Cursor> cursorLoader) {}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/782972.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大舍传媒:如何在海外新闻媒体发稿报道摩洛哥?

引言 作为媒体行业的专家&#xff0c;我将分享一些关于在海外新闻媒体发稿报道摩洛哥的干货教程。本教程将带您深入了解三个重要的新闻媒体平台&#xff1a;Mediterranean News、Morocco News和North African News。 地中海Mediterranean News Mediterranean News是一个知名…

Java中获取Class对象的三种方式

Java中获取Class对象的三种方式 1、对象调用getClass()方法2、类名.class的方式3、通过Class.forName()静态方法4、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java中&#xff0c;Class对象是一个非常重要的概念&#xff0c;它代…

高职计算机应用技术专业教学解决方案

前言 随着信息技术的飞速发展&#xff0c;计算机应用技术专业在高职教育中占据了举足轻重的地位。面对日益增长的行业需求和不断变化的教育环境&#xff0c;高职院校亟需探索创新的教学模式&#xff0c;以培养适应社会和经济发展的高素质技术技能型人才。唯众《高职计算机应用…

基于docker上安装elasticSearch7.12.1

部署elasticsearch 首先&#xff0c;先创建网络 # 创建网络 docker network create es-net拉取elasticSearch的镜像 #拉取镜像 docker pull elasticsearch:7.12.1创建挂载点目录 # 创建挂载点目录 mkdir -p /usr/local/es/data /usr/local/es/config /usr/local/es/plugin…

【Linux开发实战指南】基于UDP协议的即时聊天室:快速构建登陆、聊天与退出功能

author: bbxwg system_version: Ubuntu 22.04 Time : 2024-07-04 目录 技术简单讲解&#xff1a; UDP (User Datagram Protocol) 链表 父子进程 信号 基于UDP的即时聊天室系统&#xff1a;客户端与服务器端实现 客户端操作步骤 服务器端操作步骤 系统版本&#xff…

怎么将视频翻译免费?这篇文章告诉你5个视频翻译的方法

在探索不同文化的经典影视剧时&#xff0c;我们常常被那些精彩绝伦的台词深深吸引。 然而&#xff0c;难以理解的外语符号让我们难以完全领略其魅力。不过&#xff0c;如果你认识免费视频翻译软件的话&#xff0c;那将这些经典台词从陌生的外语符号变成直观的母语表达&#xf…

藏汉翻译通工具推荐使用《藏文翻译词典》App:藏族文化的掌上宝典,帮助你了解学习藏语反义词近义词和藏文作文!

如果你正在学习藏语&#xff0c;遇到不同地区的发音不同时&#xff0c;卫藏语、安多语和康巴语&#xff0c;那么你需要一款好用的翻译和语音朗读工具&#xff0c;帮助你掌握藏语。 如果你正在用藏文写作文&#xff0c;发现一些词汇不会时&#xff0c;需要使用藏文词典&#xf…

从0到1构建渠道运营体系:实战案例与策略指南

引言 在当今竞争激烈的市场环境中&#xff0c;有效的渠道运营是企业实现产品或服务快速触达目标用户、提升市场份额的关键。从零开始构建一个高效的渠道运营体系&#xff0c;不仅需要深思熟虑的策略规划&#xff0c;还需要灵活应变的实战操作。本文将结合实战案例&#xff0c;…

C++ STL 多线程库用法介绍

目录 一&#xff1a;Atomic&#xff1a; 二&#xff1a;Thread 1. 创建线程 2. 小心移动(std::move)线程 3. 如何创建带参数的线程 4. 线程参数是引用类型时&#xff0c;要小心谨慎。 5. 获取线程ID 6. jthread 7. 如何在线程中使用中断 stop_token 三&#xff1a;如何…

大前端热点技术

前言 2018年后&#xff0c;整个大前端发展趋于稳定&#xff0c;各大主流框架的特性变少&#xff0c;各种新轮子也在逐渐变少&#xff0c;但在多端融合、上下游提效以及一些细分领域&#xff0c;还是有很多值得期待的。 本文将基于过去一年大前端方向在Web、Node、多端、IoT、…

vue学习笔记之组件传值

说起组件传值&#xff0c;首先要介绍再vue中什么是组件。 组件&#xff08;Component&#xff09;&#xff0c;是vue中很强大的一个功能&#xff0c;可以将一些可重用的代码进行重用。所有的vue组件同时也是vue实例&#xff0c;可以接受使用相同的选项对象和提供相同的生命周期…

[Unity入门01] Unity基本操作

参考的傅老师的教程学了一下Unity的基础操作&#xff1a; [傅老師/Unity教學] Unity3D基礎入門 [華梵大學] 遊戲引擎應用基礎(Unity版本) Class#01 移动&#xff1a;鼠标中键旋转&#xff1a;鼠标右键放大&#xff1a;鼠标滚轮飞行模式&#xff1a;右键WASDQEFocus模式&…

【机器学习】属性降维:揭示数据的简化之美

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 属性降维&#xff1a;揭示数据的简化之美引言什么是属性降维&#xff1f;为何降…

linux学习week2+3

linux学习 九.linux磁盘分区、挂载 3.磁盘情况查询 命令&#xff1a;df -h 注意&#xff1a;使用率到80%以上就要清理了 查询指定目录的磁盘占用情况&#xff1a;du -d 目录 其它参数&#xff1a; -s&#xff1a;指定目录占用大小汇总 -h&#xff1a;带计量单位 -a&#xff…

lora/lycoris

Stable Diffusion 训练指南 (LyCORIS) | Coding HuskyStable Diffusion 文字生成图片的教程已经很多了。这篇文章是讲解如何用 Kohya Trainer 在 Google Colab 上训练一个 LyCORIS 模型。在读之前希望你已经至少玩过 Stable Diffusion。https://ericfu.me/stable-diffusion-fin…

QFileSystemModel绑定到 QTreeView、 QListView、QTableView

QFileSystemModel绑定到 QTreeView、 QListView、QTableView&#xff0c;实现文件的查看 .h文件 #ifndef FILESYSEXAMPLE_H #define FILESYSEXAMPLE_H#include <QMainWindow> #include <QFileSystemModel>namespace Ui { class FileSysExample; }class FileSysExam…

Vuex的模块化编程

1.之前我们使用store引入的时候不够简介&#xff0c;store为我们封装了方法 mapState:从state中获取数据,以数组的方式返回 mapGetters:从getters中获取方法,以数组的方式返回 mapMutations:从mutations中获取操作,以数组的方式返回 mapActions:从actions中获取动作,以数组的方…

Hi3861 OpenHarmony嵌入式应用入门--MQTT

MQTT 是机器对机器(M2M)/物联网(IoT)连接协议。它被设计为一个极其轻量级的发布/订阅消息传输 协议。对于需要较小代码占用空间和/或网络带宽非常宝贵的远程连接非常有用&#xff0c;是专为受限设备和低带宽、 高延迟或不可靠的网络而设计。这些原则也使该协议成为新兴的“机器…

PFC电路中MOS管的选取2

上面这种驱动方式叫推挽驱动&#xff0c;或者图腾柱驱动 当芯片驱动脚 DRV为高电平时&#xff0c;此时回路中的源是芯片的 DRV引脚&#xff0c;芯片驱动电流从左往右流动&#xff0c;通过 R1&#xff0c;通过Q1的be脚&#xff0c;通过R3、R4给MOS管Q4的Cgs结电容充电 不过值得…

Mybatis-Plus一文详解BaseMapper和Service 使用

Mybatis-Plus简介 MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个MyBatis (opens new window)的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 特性&#xff1a; 无侵入&#xff1a;只做增强不做…