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

Обзор трёх способов реализации пользовательских View в Android

В моем дипломном проекте много раз использовался пользовательский контрол, я всегда хотел подвести итоги способов реализации пользовательских контролей, сегодня я постараюсь сделать это. До этого я изучал несколько статей о пользовательских View на блоге Гуо Линя, и я чувствовал, что это было очень полезно, в этой статье я использовал некоторые из этих материалов.

В общем, реализация пользовательских контролей имеет три способа,分别是: компоновка контролей, рисованные контроли и наследование контролей. Ниже мы рассмотрим эти три способа.

(один)

Компоновка контролов, как следует из названия, это объединение некоторых小的 контролей в новый контрол, эти小的 контроли часто являются системными. Например, в большинстве приложений используется контрол заголовка, на самом деле используется компоновка контролей, поэтому ниже将通过 реализацию простого пользовательского контрола заголовка расскажем о χρήсе компоновки контролей.

1、Создайте новый проект Android, создайте файл макета для пользовательского заголовка title_bar.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:background="#0000ff" >
  <Button
    android:id="@+id/left_btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerVertical="true"
    android:layout_margin="5dp"
    android:background="@drawable/back1_64" />
  <TextView
    android:id="@+id/title_tv"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:text="Это заголовок"
    android:textColor="#ffffff"
    android:textSize="20sp" />
</RelativeLayout>

可见 этот контрол заголовка довольно прост, в левом углу есть кнопка возврата, фоном является предварительно подготовленное изображение back1_64.png, в центре заголовка находится текст заголовка.

2、Создайте класс TitleView, наследующийся от RelativeLayout:

public class TitleView extends RelativeLayout {
  // Контрол кнопки возврата
  private Button mLeftBtn;
  // Контрол текста заголовка
  private TextView mTitleTv;
  public TitleView(Context context, AttributeSet attrs) {
    super(context, attrs);
    // Загрузка макета
    LayoutInflater.from(context).inflate(R.layout.title_bar, this);
    // Получение контрола
    mLeftBtn = (Button) findViewById(R.id.left_btn);
    mTitleTv = (TextView) findViewById(R.id.title_tv);
  }
  // Добавление пользовательского события для кнопки возврата
  public void setLeftButtonListener(OnClickListener listener) {
    mLeftBtn.setOnClickListener(listener);
  }
  // Метод установки заголовка
  public void setTitleText(String title) {
    mTitleTv.setText(title);
  }
}

В TitleView主要是 загружается макет пользовательской заголовочной панели, добавляется метод слушателя событий для кнопки возврата и предоставляется метод для установки текста заголовка.

3. В файле activity_main.xml ввести пользовательскую заголовочную панель:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/main_layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical" >
  <com.example.test.TitleView
    android:id="@+id/title_bar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >
  </com.example.test.TitleView>
</LinearLayout>

4. В MainActivity получить пользовательскую заголовочную панель и добавить пользовательское событие для кнопки возврата:

private TitleView mTitleBar;
     mTitleBar = (TitleView) findViewById(R.id.title_bar);
    mTitleBar.setLeftButtonListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        Toast.makeText(MainActivity.this, "Нажата кнопка возврата", Toast.LENGTH_SHORT)
            .show();
        finish();
      }
    });

5. Результат выполнения:

   

Таким образом, с помощью комбинирования можно реализовать пользовательскую заголовочную панель, и с помощью более сложного комбинирования можно создать пользовательские контроли с более сложными функциями, например, пользовательскую строку поиска.

 (2) Пользовательский рисованный контрол

Содержимое пользовательских контролей рисуется вручную, в методе onDraw класса View. Ниже приведен пример простого счетчика, который при каждом нажатии увеличивает значение счетчика на 1 и отображает его.

1. Создать класс CounterView, наследующийся от View и реализующий интерфейс OnClickListener:

public class CounterView extends View implements OnClickListener {
  // 定义画笔
  private Paint mPaint;
  // 用于获取文字的宽和高
  private Rect mBounds;
  // 计数值,每点击一次本控件,其值增加1
  private int mCount;
  public CounterView(Context context, AttributeSet attrs) {
    super(context, attrs);
    // 初始化画笔、Rect
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mBounds = new Rect();
    // 本控件的点击事件
    setOnClickListener(this);
  }
  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPaint.setColor(Color.BLUE);
    // 绘制一个填充色为蓝色的矩形
    canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
    mPaint.setColor(Color.YELLOW);
    mPaint.setTextSize(50);
    String text = String.valueOf(mCount);
    // 获取文字的宽和高
    mPaint.getTextBounds(text, 0, text.length(), mBounds);
    float textWidth = mBounds.width();
    float textHeight = mBounds.height();
    // 绘制字符串
    canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2
        + textHeight / 2, mPaint);
  }
  @Override
  public void onClick(View v) {
    mCount ++;
    // 重绘
    invalidate();
  }
}

2、在activity_main.xml中引入该自定义布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/main_layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical" >
  <com.example.test.CounterView
    android:id="@+id/counter_view"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_gravity="center_horizontal|top"
    android:layout_margin="20dp" />
</LinearLayout>

3、Результат выполнения показан ниже:

(Третий раздел)Наследование контроллера

Это наследование существующего контроллера, создание нового контроллера, сохранение свойств родительского контроллера и возможность добавления новых свойств. Вот пример реализации поддержки горизонтального скольжения для удаления элементов списка в пользовательском ListView.

1、Создайте макет кнопки удаления delete_btn.xml, который отображается после горизонтального скольжения элемента списка:

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:background="#FF0000"
  android:padding="5dp"
  android:text="Удалить"
  android:textColor="#FFFFFF"
  android:textSize="16sp" >
</Button>

2、Создайте класс CustomListView, наследующий ListView, и реализующий интерфейсы OnTouchListener и OnGestureListener:

public class CustomListView extends ListView implements OnTouchListener,
    OnGestureListener {
  // Детектор手势 движений
  private GestureDetector mGestureDetector;
  // Листener события удаления
  public interface OnDeleteListener {
    void onDelete(int index);
  }
  private OnDeleteListener mOnDeleteListener;
  // Кнопка удаления
  private View mDeleteBtn;
  // Логика элемента списка
  private ViewGroup mItemLayout;
  // Выбранный элемент списка
  private int mSelectedItem;
  // Показана ли текущая кнопка удаления?
  private boolean isDeleteShown;
  public CustomListView(Context context, AttributeSet attrs) {
    super(context, attrs);
    // Создать объект слушателя жестов
    mGestureDetector = new GestureDetector(getContext(), this);
    // Слушать событие onTouch
    setOnTouchListener(this);
  }
  // Установить слушатель события удаления
  public void setOnDeleteListener(OnDeleteListener listener) {
    mOnDeleteListener = listener;
  }
  // Слушатель касания
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    if (isDeleteShown) {
      hideDelete();
      return false;
    } else {
      return mGestureDetector.onTouchEvent(event);
    }
  }
  @Override
  public boolean onDown(MotionEvent e) {
    if (!isDeleteShown) {
      mSelectedItem = pointToPosition((int) e.getX(), (int) e.getY());
    }
    return false;
  }
  @Override
  public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
      float velocityY) {
    // Если текущая кнопка удаления не отображается и скорость скольжения по оси X больше скорости скольжения по оси Y
    if (!isDeleteShown && Math.abs(velocityX) > Math.abs(velocityY)) {
      mDeleteBtn = LayoutInflater.from(getContext()).inflate(
          R.layout.delete_btn, null);
      mDeleteBtn.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
          mItemLayout.removeView(mDeleteBtn);
          mDeleteBtn = null;
          isDeleteShown = false;
          mOnDeleteListener.onDelete(mSelectedItem);
        }
      });
      mItemLayout = (ViewGroup) getChildAt(mSelectedItem
          - getFirstVisiblePosition());
      RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
          LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
      params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
      params.addRule(RelativeLayout.CENTER_VERTICAL);
      mItemLayout.addView(mDeleteBtn, params);
      isDeleteShown = true;
    }
    return false;
  }
  // 隐藏删除按钮
  public void hideDelete() {
    mItemLayout.removeView(mDeleteBtn);
    mDeleteBtn = null;
    isDeleteShown = false;
  }
  public boolean isDeleteShown() {
    return isDeleteShown;
  }
  /**
   * 后面几个方法本例中没有用到
   */
  @Override
  public void onShowPress(MotionEvent e) {
  }
  @Override
  public boolean onSingleTapUp(MotionEvent e) {
    return false;
  }
  @Override
  public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
      float distanceY) {
    return false;
  }
  @Override
  public void onLongPress(MotionEvent e) {
  }
}

3、定义列表项布局custom_listview_item.xml,它的结构很简单,只包含了一个TextView:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:descendantFocusability="blocksDescendants" />
  <TextView
    android:id="@+id/content_tv"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerVertical="true"
    android:layout_margin="30dp"
    android:gravity="center_vertical|left" />
</RelativeLayout>

4、定义适配器类CustomListViewAdapter,继承自ArrayAdapter<String>:

public class CustomListViewAdapter extends ArrayAdapter<String> {
  public CustomListViewAdapter(Context context, int textViewResourceId,
      List<String> objects) {
    super(context, textViewResourceId, objects);
  }
  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    View view;
    if (convertView == null) {
      view = LayoutInflater.from(getContext()).inflate(
          R.layout.custom_listview_item, null);
    } else {
      view = convertView;
    }
    TextView contentTv = (TextView) view.findViewById(R.id.content_tv);
    contentTv.setText(getItem(position));
    return view;
  }
}

5、在activity_main.xml中引入自定义的ListView:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/main_layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical" >
  <com.example.test.CustomListView
    android:id="@+id/custom_lv"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />
</LinearLayout>

6、在MainActivity中对列表做初始化、设置列表项删除按钮点击事件等处理:

public class MainActivity extends Activity {
  // 自定义Lv
  private CustomListView mCustomLv;
  // 自定义适配器
  private CustomListViewAdapter mAdapter;
  // 内容列表
  private List<String> contentList = new ArrayList<String>();
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.activity_main);
    initContentList();
    mCustomLv = (CustomListView) findViewById(R.id.custom_lv);
    mCustomLv.setOnDeleteListener(new OnDeleteListener() {
      @Override
      public void onDelete(int index) {
        contentList.remove(index);
        mAdapter.notifyDataSetChanged();
      }
    });
    mAdapter = new CustomListViewAdapter(this, 0, contentList);
    mCustomLv.setAdapter(mAdapter);
  }
  // Инициализация списка содержимого
  private void initContentList() {
    for (int i = 0; i < 20; i++) {
      contentList.add("Внутренний элемент" + i);
    }
  }
  @Override
  public void onBackPressed() {
    if (mCustomLv.isDeleteShown()) {
      mCustomLv.hideDelete();
      return;
    }
    super.onBackPressed();
  }
}

7. Результат выполнения:

Вот и все, что есть в этой статье, я надеюсь, что это поможет вам в изучении, и希望大家多多支持呐喊教程。

Заявление: содержание этой статьи взято из Интернета, авторские права принадлежат соответствующему автору, контент предоставлен пользователями Интернета в добровольном порядке, сайт не обладает правами собственности, не underwent редактирование, и не несет ответственности за связанные с этим юридические последствия. Если вы обнаружите содержимое,涉嫌侵犯版权, пожалуйста, отправьте письмо по адресу: notice#oldtoolbag.com (во время отправки письма, пожалуйста, замените # на @) для подачи жалоб и предоставления соответствующих доказательств. Если подтвердится, что это侵权内容, сайт немедленно удалят涉嫌侵权的内容。

Рекомендуется