Статьи

Добавление переключателя переднего плана в View / ViewGroup на Android

Все уже видели Android-карты с сенсорным отзывом. Селектор рисуется на переднем плане, а не на заднем плане (как мы обычно это реализуем). Этот эффект на самом деле довольно прост в реализации, и в некоторых случаях он уже реализован.

Вот скриншот эффекта состояния печати в приложении Google Play:

I. Если ваш взгляд FrameLayout

Это самый простой способ добавить селектор переднего плана, потому что для этого есть метод ! На самом деле, вам просто нужно передать свой селектор, как android:foregroundв xml или программно, вызывая setForeground(Drawable).

II. Если ваш взгляд неFrameLayout

Не волнуйся, это довольно просто. По сути, нам просто нужно установить правильное состояние селектора (нажатие, фокусировка и т. Д.), Установить границы и нарисовать его после самого вида. Таким образом, селектор будет нарисован после и, как следствие, над видом.

Смена государства

В Viewклассе метод вызывается каждый раз, когда изменяется состояние представления. Этот метод drawableStateChanged()( DOC ЗДЕСЬ ).

@Override
protected void drawableStateChanged() {
  super.drawableStateChanged();

  mForegroundSelector.setState(getDrawableState());

  //redraw
  invalidate();
}

Обновление границ рисования

Метод вызывается каждый раз, когда изменяется размер представления. Этот метод onSizeChanged(int, int, int, int)( DOC ЗДЕСЬ ).

@Override
protected void onSizeChanged(int width, int height, int oldwidth, int oldheight) {
  super.onSizeChanged(width, height, oldwidth, oldheight);

  mForegroundSelector.setBounds(0, 0, width, height);
}

Рисование селектора

Есть 2 случая:

1. Ваше мнение не являетсяViewGroup

Селектор должен быть нарисован после вызова onDraw(Canvas canvas)

@Override
protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);

  mForegroundSelector.draw(canvas);
}

2. Ваше мнение ViewGroup

Селектор должен быть нарисован после всех его детей, то есть после вызова dispatchDraw(Canvas canvas)

@Override
protected void dispatchDraw(Canvas canvas) {
  super.dispatchDraw(canvas);

  mForegroundSelector.draw(canvas);
}

Рисование анимированного рисунка

Если ваш рисованный рисунок анимирован, вам нужно сделать еще кое-что. Давайте предположим, что у нас есть селектор с атрибутом android:exitFadeDuration. Это означает, что когда селектор меняет свое состояние, старое состояние исчезает.

Сначала мы должны переместить метод draw селектора из onDraw()(для представлений) или dispatchDraw()(для ViewGroups) в draw(Canvas)метод, вот так:

@Override
public void draw(Canvas canvas) {
  super.draw(canvas);

  mForegroundDrawable.draw(canvas);
}

Затем мы должны переопределить, jumpDrawablesToCurrentStateчтобы указать наш селектор, чтобы сделать анимацию перехода между состояниями, и verifyDrawableчтобы указать представление, которое мы отображаем в нашей собственной прорисовке.

@Override
protected boolean verifyDrawable(Drawable who) {
  return super.verifyDrawable(who) || (who == mForegroundDrawable);
}

@TargetApi(11)
@Override
public void jumpDrawablesToCurrentState() {
  super.jumpDrawablesToCurrentState();
  mForegroundDrawable.jumpToCurrentState();
}

Но что делает jumpToCurrentState ()? Давайте посмотрим немного исходного кода, в DrawableContainerклассе.

@Override
public void jumpToCurrentState() {
    boolean changed = false;
    if (mLastDrawable != null) {
        mLastDrawable.jumpToCurrentState();
        mLastDrawable = null;
        changed = true;
    }
    if (mCurrDrawable != null) {
        mCurrDrawable.jumpToCurrentState();
        mCurrDrawable.mutate().setAlpha(mAlpha);
    }
    if (mExitAnimationEnd != 0) {
        mExitAnimationEnd = 0;
        changed = true;
    }
    if (mEnterAnimationEnd != 0) {
        mEnterAnimationEnd = 0;
        changed = true;
    }
    if (changed) {
        invalidateSelf();
    }
}

Мы можем заметить, что jumpToCurrentState()звонки invalidateSelf(). И вот invalidateSelf()исходный код метода:

/**
 * Use the current {@link Callback} implementation to have this Drawable
 * redrawn.  Does nothing if there is no Callback attached to the
 * Drawable.
 *
 * @see Callback#invalidateDrawable
 * @see #getCallback() 
 * @see #setCallback(android.graphics.drawable.Drawable.Callback) 
 */
public void invalidateSelf() {
    final Callback callback = getCallback();
    if (callback != null) {
        callback.invalidateDrawable(this);
    }
}

Мы ясно видим, что если обратный вызов не установлен, рисование не будет перерисовано. Итак, давайте установим обратный вызов, когда мы начнем наш селектор.

private void init(Context context) {
  mForegroundDrawable = getResources().getDrawable(R.drawable.myselector);

  //set a callback, or the selector won't be animated
  mForegroundDrawable.setCallback(this);
}

Ваш селектор должен исчезнуть сейчас!

EXTRA

Получить фон по умолчанию и установить его в качестве переднего плана

Вы можете получить фоновый селектор по умолчанию для вашей темы и установить его в качестве основного, если хотите:

TypedArray a = getContext().obtainStyledAttributes(new int[]{android.R.attr.selectableItemBackground});
mForegroundDrawable = a.getDrawable(0);
if (mForegroundDrawable != null) {
  mForegroundDrawable.setCallback(this);
}
a.recycle();