Добавляем кнопки при свайпе в RecyclerView

Введение

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

Отличным инструментом для создания продуманного интерфейса в Android являются свайпы (Swipe). С их помощью всего лишь одним-двумя жестами можно выполнять самые разные действия с объектами: перемещать их по экрану, удалять, изменять и много другое. Например, можно с помощью свайпа выводить на экран приложения такие элементы интерфейса, как боковое меню.

В этой статье мы рассмотрим, как добавить свайп для виджета CardView, расположенного внутри RecyclerView, который будет выводить с правой стороны карточки кнопку. Для примера делать мы это будем в одном из наших приложений Менеджер паролей от Wi-Fi сетей.

Задачей здесь является сделать кнопку, которая будет перемещать сети из списка активных в Архив (или удалять, если сеть уже находится в Архиве).

Создание RecyclerView

Поскольку список сетей может быть довольно большим, в приложении используется виджет RecyclerView. Чтобы использовать его в своём проекте, нужно для начала добавить зависимость. Для этого в файле build.gradle модуля приложения в dependencies нужно добавить следующую строку:

dependencies {
  ...
  compile "com.android.support:recyclerview-v7:27.0.2"
  ...
}

После этого нужно разместить виджет на разметке активности. Пример этого можно увидеть ниже:

<android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    />

Наше приложение отображает список сетей, сохранённых пользователем. Чтобы RecyclerView смог выводить этот список, для начала нужен класс, который будет хранить данные о сетях. Для этих целей используется класс WifiInfo, содержащий следующий код:

public class WifiInfo implements Serializable {
  public WifiInfo(String SSID, String password, String type, boolean hidden, Date date,
      String state) {
    this.SSID = SSID;
    this.password = password;
    this.type = type;
    this.hidden = hidden;
    this.date = date;
    this.state = state;
  }

  public String ID;
  public String SSID;
  public String password;
  public String type;
  public boolean hidden;
  public String state;
  public Date date;

  public WifiInfo() {
  }


  @Exclude public Map<String, Object> toMap() {
    HashMap<String, Object> result = new HashMap<>();
    result.put("ID", ID);
    result.put("SSID", SSID);
    result.put("password", password);
    result.put("type", type);
    result.put("hidden", hidden);
    result.put("date", date);
    result.put("state", state);

    return result;
  }

  @Override public boolean equals(Object obj) {
    if (obj instanceof WifiInfo) {
      WifiInfo temp = (WifiInfo) obj;
      if (this.SSID.equals(temp.SSID) && this.password.equals(temp.password) && this.type.equals(
          temp.type) && this.hidden == temp.hidden) {
        return true;
      }
    }
    return false;
  }

  @Override public int hashCode() {
    return (this.SSID.hashCode() + this.password.hashCode() + this.type.hashCode());
  }
}

Следующим этапом является создание адаптера. Класс RecyclerView.Adapter крайне важен здесь, поскольку благодаря ему данные, хранящиеся в классе WifiInfo, будут выводиться в итоговый список. В классе нужно переопределить несколько методов, таких как:

  • getItemCount() — возвращает количество элементов, которое мы хотим отобразить;
  • onCreateViewHolder() — создает экземпляр класса RecyclerView.ViewHolder и создает разметку. Он вызывается только тогда, когда RecyclerView требуется добавить в список новый объект.
  • onBindViewHolder() — привязывает данные к разметке ViewHolder. Он вызывается тогда, когда RecyclerView заполняет объект данными.

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

public class PasswordAdapter extends RecyclerView.Adapter<PasswordAdapter.PasswordViewHolder> {
  public interface dbCallBack {
    void removeFromDb(WifiInfo wi);

    void addToDbCB(WifiInfo wi);

    void moveToHistory(WifiInfo wi);
  }

  private List<WifiInfo> passwordList, passwordListCopy;
  private Context context;
  private dbCallBack db;
  private LinearLayout desertPlaceholder;
  private boolean cardHide = true;
  private Drawable copy, share;
  private int collapsedHeight;

  PasswordAdapter(Context context, dbCallBack db, LinearLayout desertPlaceholder) {
    this.context = context;
    this.passwordList = new ArrayList<>();//new ArrayList<>(passwordList);
    passwordListCopy = new ArrayList<>();
    this.db = db;
    this.desertPlaceholder = desertPlaceholder;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      copy = ContextCompat.getDrawable(context, R.drawable.ic_content_copy_black_24px);
      share = ContextCompat.getDrawable(context, R.drawable.ic_share_black_24px);
    } else {
      copy =
          VectorDrawableCompat.create(context.getResources(), R.drawable.ic_content_copy_black_24px,
              context.getTheme());
      share = VectorDrawableCompat.create(context.getResources(), R.drawable.ic_share_black_24px,
          context.getTheme());
    }
  }

  void filter(String text) {
    passwordList.clear();
    if (text.isEmpty()) {
      passwordList.addAll(passwordListCopy);
    } else {
      text = text.toLowerCase();
      for (WifiInfo item : passwordListCopy) {
        if (item.SSID.toLowerCase().contains(text)) {
          passwordList.add(item);
        }
      }
    }
    notifyDataSetChanged();
  }

  @Override public int getItemCount() {
    desertPlaceholder.setVisibility(passwordList.size() > 0 ? View.GONE : View.VISIBLE);
    return passwordList.size();
  }

  @Override public void onBindViewHolder(final PasswordViewHolder passVH, int i) {
    final WifiInfo wi = passwordList.get(i);
    passVH.vSSID.setText(wi.SSID);
    passVH.vPassword.setText(wi.password);

    String pattern = ((SimpleDateFormat) DateFormat.getDateInstance(DateFormat.MEDIUM,
        Locale.getDefault())).toPattern();
    DateFormat dateFormat = new SimpleDateFormat(pattern, Locale.getDefault());

    if (passVH.swipeLayout.isOpened()) {
      passVH.swipeLayout.close(false);
    }

    if (!cardHide) {
      passVH.cardMore.setVisibility(View.GONE);
      passVH.delete.setLayoutParams(
          new FrameLayout.LayoutParams(passVH.delete.getWidth(), collapsedHeight));
      cardHide = true;
      checkArrow(passVH.cardArrow);
    }

    passVH.ivShare.setImageDrawable(share);
    passVH.ivCopy.setImageDrawable(copy);
    passVH.vDate.setText(dateFormat.format(wi.date));
    if (wi.hidden) {
      passVH.vHide.setVisibility(View.VISIBLE);
      passVH.vDot.setVisibility(View.VISIBLE);
    }
    if (wi.state.equals("active")) {
      Drawable drawable;
      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        drawable =
            VectorDrawableCompat.create(context.getResources(), R.drawable.ic_archive_black_24px,
                context.getTheme());
      } else {
        drawable = ContextCompat.getDrawable(context, R.drawable.ic_archive_black_24px);
      }
      passVH.ivDelete.setImageDrawable(drawable);
      passVH.vDelete.setText(context.getString(R.string.to_archive));
      passVH.delete.setOnClickListener(new View.OnClickListener() {
        @Override public void onClick(View view) {
          db.moveToHistory(wi);
          passVH.swipeLayout.close(true);
        }
      });
    } else {
      Drawable drawable;
      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        drawable =
            VectorDrawableCompat.create(context.getResources(), R.drawable.ic_delete_black_24px,
                context.getTheme());
      } else {
        drawable = ContextCompat.getDrawable(context, R.drawable.ic_delete_black_24px);
      }
      passVH.ivDelete.setImageDrawable(drawable);
      passVH.vDelete.setText(context.getString(R.string.delete));
      passVH.delete.setOnClickListener(new View.OnClickListener() {
        @Override public void onClick(View v) {
          App.selectContent("card", "удалить");
          removeItem(wi);
          db.removeFromDb(wi);
          Snackbar.make(passVH.itemView, wi.SSID + " " + context.getString(R.string.removed),
              Snackbar.LENGTH_LONG).setAction(R.string.cancel, new View.OnClickListener() {
            @Override public void onClick(View v) {
              addItem(wi);
              db.addToDbCB(wi);
            }
          }).show();
        }
      });
    }
    passVH.vCopy.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
        App.selectContent("card", "копировать");
        Tools.CopyToClipboard(context, wi.password);
        Toast.makeText(context, R.string.Copy_value, Toast.LENGTH_LONG).show();
      }
    });
    passVH.vConnect.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
        App.selectContent("card", "подключить сеть");
        WifiManager wifiManager =
            (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
        if (wifiManager == null) {
          return;
        }
        final Activity activity = (Activity) context;
        activity.runOnUiThread(new Runnable() {
          @Override public void run() {
            Toast.makeText(context, R.string.wifi_changing_network, Toast.LENGTH_SHORT).show();
          }
        });
        WifiConfigManager wcf = new WifiConfigManager(wifiManager);
        if (wcf.getStatus().toString().equals("PENDING")) wcf.execute(wi);
      }
    });
    passVH.vShare.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
        PopupMenu popupMenu = new PopupMenu(context, v);
        MenuInflater menuInflater = popupMenu.getMenuInflater();
        PopUpMenuEventHandle popUpMenuEventHandle = new PopUpMenuEventHandle(context, wi);
        popupMenu.setOnMenuItemClickListener(popUpMenuEventHandle);
        menuInflater.inflate(R.menu.share_popup, popupMenu.getMenu());
        popupMenu.show();
      }
    });
    passVH.cardMain.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View view) {
        if (cardHide) {
          passVH.cardMore.setVisibility(View.VISIBLE);
          collapsedHeight = passVH.cardMain.getHeight();
          passVH.delete.setLayoutParams(new FrameLayout.LayoutParams(passVH.delete.getWidth(),
              ViewGroup.LayoutParams.MATCH_PARENT));
          cardHide = false;
        } else {
          passVH.cardMore.setVisibility(View.GONE);
          passVH.delete.setLayoutParams(
              new FrameLayout.LayoutParams(passVH.delete.getWidth(), collapsedHeight));
          cardHide = true;
        }
        checkArrow(passVH.cardArrow);
      }
    });
  }

  @Override public PasswordViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
    View itemView = LayoutInflater.
        from(viewGroup.getContext()).
        inflate(R.layout.card_network, viewGroup, false);
    return new PasswordViewHolder(itemView);
  }

  private void checkArrow(ImageView imageView) {
    if (cardHide) {
      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        imageView.setImageDrawable(VectorDrawableCompat.create(context.getResources(),
            R.drawable.ic_keyboard_arrow_down_black_24dp, context.getTheme()));
      } else {
        imageView.setImageDrawable(
            ContextCompat.getDrawable(context, R.drawable.ic_keyboard_arrow_down_black_24dp));
      }
    } else {
      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        imageView.setImageDrawable(VectorDrawableCompat.create(context.getResources(),
            R.drawable.ic_keyboard_arrow_up_black_24dp, context.getTheme()));
      } else {
        imageView.setImageDrawable(
            ContextCompat.getDrawable(context, R.drawable.ic_keyboard_arrow_up_black_24dp));
      }
    }
  }

  public List<WifiInfo> getList() {
    return passwordList;
  }

  public void addAll(List<WifiInfo> list) {
    passwordList.addAll(list);
    passwordListCopy.addAll(list);
    notifyDataSetChanged();
  }

  public void clearAll() {
    passwordList.clear();
    passwordListCopy.clear();
    notifyDataSetChanged();
  }

  WifiInfo removeItem(int position) {
    WifiInfo model = passwordList.remove(position);
    passwordListCopy.remove(position);
    notifyItemRemoved(position);
    notifyItemRangeChanged(position, passwordList.size());
    return model;
  }

  void removeItem(final WifiInfo model) {
    int pos = passwordList.indexOf(model);
    if (pos > -1 && pos < passwordList.size()) {
      passwordList.remove(model);
      passwordListCopy.remove(model);
      notifyItemRemoved(pos);
    }
  }

  void addItem(WifiInfo model) {
    if (!passwordListCopy.contains(model)) {
      passwordList.add(model);
      passwordListCopy.add(model);
      Tools.sort(passwordList);
      Tools.sort(passwordListCopy);
      notifyItemInserted(passwordList.indexOf(model));
    }
  }

  public void moveItem(int fromPosition, int toPosition) {
    final WifiInfo model = passwordList.remove(fromPosition);
    passwordList.add(toPosition, model);
    notifyItemMoved(fromPosition, toPosition);
  }

  public WifiInfo getItem(int position) {
    return passwordList.get(position);
  }

  static class PasswordViewHolder extends RecyclerView.ViewHolder {
    SwipeRevealLayout swipeLayout;
    LinearLayout cardMain, cardMore, vCopy, vShare;
    TextView vSSID, vPassword, vHide, vDate, vConnect, vDot, vDelete;
    FrameLayout delete;
    ImageView cardArrow, ivCopy, ivShare, ivDelete;

    PasswordViewHolder(View v) {
      super(v);
      vDelete = v.findViewById(R.id.tv_delete);
      ivDelete = v.findViewById(R.id.iv_delete);
      delete = v.findViewById(R.id.delete_layout);
      swipeLayout = v.findViewById(R.id.swipe_layout);
      cardMain = v.findViewById(R.id.card_main);
      cardMore = v.findViewById(R.id.card_more);
      vSSID = v.findViewById(R.id.SSID);
      vPassword = v.findViewById(R.id.password);
      vHide = v.findViewById(R.id.tv_hide);
      vCopy = v.findViewById(R.id.CopyButton);
      vConnect = v.findViewById(R.id.ConnectButton);
      vShare = v.findViewById(R.id.ShareButton);
      vDate = v.findViewById(R.id.date);
      cardArrow = v.findViewById(R.id.card_arrow);
      vDot = v.findViewById(R.id.tv_dot);
      ivCopy = v.findViewById(R.id.iv_card_copy);
      ivShare = v.findViewById(R.id.iv_card_share);
      vPassword.setCompoundDrawablesWithIntrinsicBounds(
          AppCompatResources.getDrawable(vPassword.getContext(), R.drawable.ic_vpn_key_black_24dp),
          null, null, null);
    }
  }
}

Здесь же, как можно увидеть, создаётся класс ViewHolder, который описывает разметку элемента и метаданные о его месте в RecyclerView. Реализация адаптера должна использовать ViewHolder в качестве подкласса, поскольку в них кешируются затратные по ресурсам операции findViewById().

Теперь нужно создать разметку, которая будет подгружаться во ViewHolder. Она представляет собой два виджета FrameLayout, помещённых внутри кастомного SwipeRevealLayout, о котором будет сказано позднее. Код разметки вы можете увидеть ниже.

<?xml version="1.0" encoding="utf-8"?>
<com.rusdelphi.wifipassword.SwipeRevealLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/swipe_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="2dp"
    android:layout_marginTop="2dp"
    app:dragEdge="right"
    app:mode="same_level"
    >

  <FrameLayout
      android:id="@+id/delete_layout"
      android:layout_width="wrap_content"
      android:layout_height="match_parent"
      android:background="@color/btn_delete"
      >

    <LinearLayout
        android:layout_width="70dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:orientation="vertical"
        >
      <ImageView
          android:id="@+id/iv_delete"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_gravity="center_horizontal"
          android:tint="@color/white"
          app:srcCompat="@drawable/ic_archive_black_24px"
          />
      <TextView
          android:id="@+id/tv_delete"
          android:layout_width="60dp"
          android:layout_height="wrap_content"
          android:layout_gravity="center_horizontal"
          android:gravity="center"
          android:text="@string/to_archive"
          android:textColor="@color/white"
          />
    </LinearLayout>

  </FrameLayout>

  <FrameLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      >

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >

      <LinearLayout
          android:id="@+id/card_main"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:orientation="vertical"
          >

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginEnd="16dp"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="14dp"
            android:orientation="horizontal"
            android:weightSum="1"
            >

          <TextView
              android:id="@+id/SSID"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              android:layout_gravity="center_vertical"
              android:layout_weight="0.55"
              android:fontFamily="sans-serif"
              android:text="Network"
              android:textColor="@android:color/black"
              android:textSize="16sp"
              android:textStyle="bold"
              />

          <TextView
              android:id="@+id/tv_dot"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              android:layout_gravity="center_vertical"
              android:layout_weight="0.05"
              android:gravity="center"
              android:text="@string/dot"
              android:visibility="invisible"
              />

          <TextView
              android:id="@+id/tv_hide"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              android:layout_gravity="center_vertical"
              android:layout_marginLeft="8dp"
              android:layout_marginStart="8dp"
              android:layout_weight="0.3"
              android:text="@string/card_hidden"
              android:textSize="16sp"
              android:visibility="invisible"
              />

          <ImageView
              android:id="@+id/card_arrow"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              android:layout_gravity="center_vertical"
              android:layout_weight="0.1"
              app:srcCompat="@drawable/ic_keyboard_arrow_down_black_24dp"
              />

        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="14dp"
            android:layout_marginEnd="16dp"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="2dp"
            android:orientation="horizontal"
            >

          <TextView
              android:id="@+id/password"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:drawablePadding="8dp"
              android:fontFamily="sans-serif"
              android:text="password"
              android:textIsSelectable="true"
              android:textSize="14sp"
              />

          <TextView
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_marginLeft="8dp"
              android:layout_marginStart="8dp"
              android:text="@string/dot"
              />

          <TextView
              android:id="@+id/date"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_marginLeft="8dp"
              android:layout_marginStart="8dp"
              android:bufferType="spannable"
              android:fontFamily="sans-serif"
              android:text="date"
              android:textIsSelectable="true"
              android:textSize="14sp"
              />

        </LinearLayout>

        <LinearLayout
            android:id="@+id/card_more"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="15dp"
            android:animateLayoutChanges="true"
            android:orientation="vertical"
            android:visibility="gone"
            >
          <Button
              android:id="@+id/ConnectButton"
              android:layout_width="match_parent"
              android:layout_height="36dp"
              android:layout_marginBottom="16dp"
              android:layout_marginEnd="16dp"
              android:layout_marginLeft="16dp"
              android:layout_marginRight="16dp"
              android:layout_marginStart="16dp"
              android:background="@color/green_settings"
              android:text="@string/connect"
              android:textSize="14sp"
              style="@style/Base.TextAppearance.AppCompat.Widget.Button.Colored"
              />

          <View
              android:layout_width="match_parent"
              android:layout_height="1dp"
              android:layout_marginBottom="11dp"
              android:layout_marginEnd="16dp"
              android:layout_marginLeft="16dp"
              android:layout_marginRight="16dp"
              android:layout_marginStart="16dp"
              android:background="@color/divider_gray"
              />

          <LinearLayout
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_marginEnd="16dp"
              android:layout_marginLeft="16dp"
              android:layout_marginRight="16dp"
              android:layout_marginStart="16dp"
              android:baselineAligned="false"
              android:orientation="horizontal"
              android:weightSum="1"
              >
            <LinearLayout
                android:id="@+id/CopyButton"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="0.5"
                android:gravity="center"
                android:orientation="horizontal"
                >
              <ImageView
                  android:id="@+id/iv_card_copy"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_gravity="center_vertical"
                  />
              <TextView
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_gravity="center_vertical"
                  android:layout_marginLeft="10dp"
                  android:layout_marginStart="10dp"
                  android:text="@string/copy_password"
                  android:textSize="14sp"
                  />
            </LinearLayout>
            <LinearLayout
                android:id="@+id/ShareButton"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="0.5"
                android:gravity="center"
                android:orientation="horizontal"
                >
              <ImageView
                  android:id="@+id/iv_card_share"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_gravity="center_vertical"
                  />
              <TextView
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_gravity="center_vertical"
                  android:layout_marginLeft="10dp"
                  android:layout_marginStart="10dp"
                  android:text="@string/share_network"
                  android:textSize="14sp"
                  />
            </LinearLayout>
          </LinearLayout>
        </LinearLayout>
      </LinearLayout>

    </android.support.v7.widget.CardView>
  </FrameLayout>
</com.rusdelphi.wifipassword.SwipeRevealLayout>

Наконец, нужно создать экземпляр класса RecyclerView в коде активности и инициализировать его. Для правильной работы нужно настроить две вещи: адаптер, который будет предоставлять данные, и менеджер разметки (LayoutManager), который говорит виджету, как нужно отобразить объекты в списке. В результате создание выглядит следующим образом:

private RecyclerView mRecyclerView;
private PasswordAdapter mPasswordAdapter;
...
mRecyclerView = findViewById(R.id.recyclerView);
...
mPasswordAdapter = new PasswordAdapter(this, this, placeholder);
mRecyclerView.setLayoutManager(new GridLayoutManager(this, 1));
RecyclerView.ItemAnimator itemAnimator = new DefaultItemAnimator();
itemAnimator.setAddDuration(500);
itemAnimator.setRemoveDuration(500);
mRecyclerView.setItemAnimator(itemAnimator);
mRecyclerView.setAdapter(mPasswordAdapter);

Добавление класса для свайпа

Теперь можно приступить к реализации свайпа для элементов списка. Для этого мы использовали в приложении исходный код библиотеки SwipeRevealLayout, которую вы можете скачать на GitHub по следующей ссылке. Стоит заметить, что способ реализации подобных свайпов в сети достаточно много, этот способ был выбран как самый оптимальный для наших задач.

Как уже говорилось, этот класс используется два FrameLayout, на одном из которых размещается собственно элемента списка, а на втором то, что должно отображаться при свайпе.

Библиотека поддерживает свайпы в любом направлении (слева, справа, сверху, снизу), а также имеет два режима:

  • normal — дополнительный элемент рисуется позади основного;
  • same level — дополнительный элемент рисуется на том же уровне, что и основной.

В библиотеке также используется дополнительный класс ViewBinderHolder, задачей которого является сохранения состояния свайпа и его восстановление. В нашем приложении в этом нет особой необходимости, поэтому этот класс был выкинут вместе с некоторыми методами основного класса. В результате код класса SwipeRevealLayout получился следующим:

@SuppressLint("RtlHardcoded") public class SwipeRevealLayout extends ViewGroup {
  protected static final int STATE_CLOSE = 0;
  protected static final int STATE_CLOSING = 1;
  protected static final int STATE_OPEN = 2;
  protected static final int STATE_OPENING = 3;
  protected static final int STATE_DRAGGING = 4;
  private static final int DEFAULT_MIN_FLING_VELOCITY = 300; // dp per second
  private static final int DEFAULT_MIN_DIST_REQUEST_DISALLOW_PARENT = 1; // dp
  public static final int DRAG_EDGE_LEFT = 0x1;
  public static final int DRAG_EDGE_RIGHT = 0x1 << 1;
  public static final int DRAG_EDGE_TOP = 0x1 << 2;
  public static final int DRAG_EDGE_BOTTOM = 0x1 << 3;
  public static final int MODE_NORMAL = 0;
  public static final int MODE_SAME_LEVEL = 1;
  private View mMainView;
  private View mSecondaryView;
  private Rect mRectMainClose = new Rect();
  private Rect mRectMainOpen = new Rect();
  private Rect mRectSecClose = new Rect();
  private Rect mRectSecOpen = new Rect();
  private int mMinDistRequestDisallowParent = 0;
  private boolean mIsOpenBeforeInit = false;
  private volatile boolean mAborted = false;
  private volatile boolean mIsScrolling = false;
  private volatile boolean mLockDrag = false;
  private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY;
  private int mState = STATE_CLOSE;
  private int mMode = MODE_NORMAL;
  private int mLastMainLeft = 0;
  private int mLastMainTop = 0;
  private int mDragEdge = DRAG_EDGE_LEFT;
  private ViewDragHelper mDragHelper;
  private GestureDetectorCompat mGestureDetector;
  private SwipeListener mSwipeListener;

  public interface SwipeListener {
    void onClosed(SwipeRevealLayout view);

    void onOpened(SwipeRevealLayout view);

    void onSlide(SwipeRevealLayout view, float slideOffset);
  }

  public static class SimpleSwipeListener implements SwipeListener {
    @Override public void onClosed(SwipeRevealLayout view) {
    }

    @Override public void onOpened(SwipeRevealLayout view) {
    }

    @Override public void onSlide(SwipeRevealLayout view, float slideOffset) {
    }
  }

  public SwipeRevealLayout(Context context) {
    super(context);
    init(context, null);
  }

  public SwipeRevealLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context, attrs);
  }

  public SwipeRevealLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
  }

  @Override public boolean onTouchEvent(MotionEvent event) {
    mGestureDetector.onTouchEvent(event);
    mDragHelper.processTouchEvent(event);
    return true;
  }

  @Override public boolean onInterceptTouchEvent(MotionEvent ev) {
    mDragHelper.processTouchEvent(ev);
    mGestureDetector.onTouchEvent(ev);

    boolean settling = mDragHelper.getViewDragState() == ViewDragHelper.STATE_SETTLING;
    boolean idleAfterScrolled =
        mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE && mIsScrolling;

    return settling || idleAfterScrolled;
  }

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

    // get views
    if (getChildCount() >= 2) {
      mSecondaryView = getChildAt(0);
      mMainView = getChildAt(1);
    } else if (getChildCount() == 1) {
      mMainView = getChildAt(0);
    }
  }

  @SuppressWarnings("ConstantConditions") @Override protected void onLayout(boolean changed, int l,
      int t, int r, int b) {
    mAborted = false;

    for (int index = 0; index < getChildCount(); index++) {
      final View child = getChildAt(index);

      int left, right, top, bottom;
      left = right = top = bottom = 0;

      final int minLeft = getPaddingLeft();
      final int maxRight = Math.max(r - getPaddingRight() - l, 0);
      final int minTop = getPaddingTop();
      final int maxBottom = Math.max(b - getPaddingBottom() - t, 0);

      int measuredChildHeight = child.getMeasuredHeight();
      int measuredChildWidth = child.getMeasuredWidth();

      // need to take account if child size is match_parent
      final LayoutParams childParams = child.getLayoutParams();
      boolean matchParentHeight = false;
      boolean matchParentWidth = false;

      if (childParams != null) {
        matchParentHeight = (childParams.height == LayoutParams.MATCH_PARENT) || (childParams.height
            == LayoutParams.FILL_PARENT);
        matchParentWidth = (childParams.width == LayoutParams.MATCH_PARENT) || (childParams.width
            == LayoutParams.FILL_PARENT);
      }

      if (matchParentHeight) {
        measuredChildHeight = maxBottom - minTop;
        childParams.height = measuredChildHeight;
      }

      if (matchParentWidth) {
        measuredChildWidth = maxRight - minLeft;
        childParams.width = measuredChildWidth;
      }

      switch (mDragEdge) {
        case DRAG_EDGE_RIGHT:
          left = Math.max(r - measuredChildWidth - getPaddingRight() - l, minLeft);
          top = Math.min(getPaddingTop(), maxBottom);
          right = Math.max(r - getPaddingRight() - l, minLeft);
          bottom = Math.min(measuredChildHeight + getPaddingTop(), maxBottom);
          break;

        case DRAG_EDGE_LEFT:
          left = Math.min(getPaddingLeft(), maxRight);
          top = Math.min(getPaddingTop(), maxBottom);
          right = Math.min(measuredChildWidth + getPaddingLeft(), maxRight);
          bottom = Math.min(measuredChildHeight + getPaddingTop(), maxBottom);
          break;

        case DRAG_EDGE_TOP:
          left = Math.min(getPaddingLeft(), maxRight);
          top = Math.min(getPaddingTop(), maxBottom);
          right = Math.min(measuredChildWidth + getPaddingLeft(), maxRight);
          bottom = Math.min(measuredChildHeight + getPaddingTop(), maxBottom);
          break;

        case DRAG_EDGE_BOTTOM:
          left = Math.min(getPaddingLeft(), maxRight);
          top = Math.max(b - measuredChildHeight - getPaddingBottom() - t, minTop);
          right = Math.min(measuredChildWidth + getPaddingLeft(), maxRight);
          bottom = Math.max(b - getPaddingBottom() - t, minTop);
          break;
      }

      child.layout(left, top, right, bottom);
    }

    // taking account offset when mode is SAME_LEVEL
    if (mMode == MODE_SAME_LEVEL) {
      switch (mDragEdge) {
        case DRAG_EDGE_LEFT:
          mSecondaryView.offsetLeftAndRight(-mSecondaryView.getWidth());
          break;

        case DRAG_EDGE_RIGHT:
          mSecondaryView.offsetLeftAndRight(mSecondaryView.getWidth());
          break;

        case DRAG_EDGE_TOP:
          mSecondaryView.offsetTopAndBottom(-mSecondaryView.getHeight());
          break;

        case DRAG_EDGE_BOTTOM:
          mSecondaryView.offsetTopAndBottom(mSecondaryView.getHeight());
      }
    }

    initRects();

    if (mIsOpenBeforeInit) {
      open(false);
    } else {
      close(false);
    }

    mLastMainLeft = mMainView.getLeft();
    mLastMainTop = mMainView.getTop();
  }

  @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (getChildCount() < 2) {
      throw new RuntimeException("Layout must have two children");
    }

    final LayoutParams params = getLayoutParams();

    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

    int desiredWidth = 0;
    int desiredHeight = 0;

    // first find the largest child
    for (int i = 0; i < getChildCount(); i++) {
      final View child = getChildAt(i);
      measureChild(child, widthMeasureSpec, heightMeasureSpec);
      desiredWidth = Math.max(child.getMeasuredWidth(), desiredWidth);
      desiredHeight = Math.max(child.getMeasuredHeight(), desiredHeight);
    }
    // create new measure spec using the largest child width
    widthMeasureSpec = MeasureSpec.makeMeasureSpec(desiredWidth, widthMode);
    heightMeasureSpec = MeasureSpec.makeMeasureSpec(desiredHeight, heightMode);

    final int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
    final int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);

    for (int i = 0; i < getChildCount(); i++) {
      final View child = getChildAt(i);
      final LayoutParams childParams = child.getLayoutParams();

      if (childParams != null) {
        if (childParams.height == LayoutParams.MATCH_PARENT) {
          child.setMinimumHeight(measuredHeight);
        }

        if (childParams.width == LayoutParams.MATCH_PARENT) {
          child.setMinimumWidth(measuredWidth);
        }
      }

      measureChild(child, widthMeasureSpec, heightMeasureSpec);
      desiredWidth = Math.max(child.getMeasuredWidth(), desiredWidth);
      desiredHeight = Math.max(child.getMeasuredHeight(), desiredHeight);
    }

    // taking accounts of padding
    desiredWidth += getPaddingLeft() + getPaddingRight();
    desiredHeight += getPaddingTop() + getPaddingBottom();

    // adjust desired width
    if (widthMode == MeasureSpec.EXACTLY) {
      desiredWidth = measuredWidth;
    } else {
      if (params.width == LayoutParams.MATCH_PARENT) {
        desiredWidth = measuredWidth;
      }

      if (widthMode == MeasureSpec.AT_MOST) {
        desiredWidth = (desiredWidth > measuredWidth) ? measuredWidth : desiredWidth;
      }
    }

    // adjust desired height
    if (heightMode == MeasureSpec.EXACTLY) {
      desiredHeight = measuredHeight;
    } else {
      if (params.height == LayoutParams.MATCH_PARENT) {
        desiredHeight = measuredHeight;
      }

      if (heightMode == MeasureSpec.AT_MOST) {
        desiredHeight = (desiredHeight > measuredHeight) ? measuredHeight : desiredHeight;
      }
    }

    setMeasuredDimension(desiredWidth, desiredHeight);
  }

  @Override public void computeScroll() {
    if (mDragHelper.continueSettling(true)) {
      ViewCompat.postInvalidateOnAnimation(this);
    }
  }

  public void open(boolean animation) {
    mIsOpenBeforeInit = true;
    mAborted = false;

    if (animation) {
      mState = STATE_OPENING;
      mDragHelper.smoothSlideViewTo(mMainView, mRectMainOpen.left, mRectMainOpen.top);
    } else {
      mState = STATE_OPEN;
      mDragHelper.abort();

      mMainView.layout(mRectMainOpen.left, mRectMainOpen.top, mRectMainOpen.right,
          mRectMainOpen.bottom);

      mSecondaryView.layout(mRectSecOpen.left, mRectSecOpen.top, mRectSecOpen.right,
          mRectSecOpen.bottom);
    }

    ViewCompat.postInvalidateOnAnimation(SwipeRevealLayout.this);
  }

  public void close(boolean animation) {
    mIsOpenBeforeInit = false;
    mAborted = false;

    if (animation) {
      mState = STATE_CLOSING;
      mDragHelper.smoothSlideViewTo(mMainView, mRectMainClose.left, mRectMainClose.top);
    } else {
      mState = STATE_CLOSE;
      mDragHelper.abort();

      mMainView.layout(mRectMainClose.left, mRectMainClose.top, mRectMainClose.right,
          mRectMainClose.bottom);

      mSecondaryView.layout(mRectSecClose.left, mRectSecClose.top, mRectSecClose.right,
          mRectSecClose.bottom);
    }

    ViewCompat.postInvalidateOnAnimation(SwipeRevealLayout.this);
  }

  public boolean isOpened() {
    return (mState == STATE_OPEN);
  }

  public boolean isClosed() {
    return (mState == STATE_CLOSE);
  }

  private int getMainOpenLeft() {
    switch (mDragEdge) {
      case DRAG_EDGE_LEFT:
        return mRectMainClose.left + mSecondaryView.getWidth();

      case DRAG_EDGE_RIGHT:
        return mRectMainClose.left - mSecondaryView.getWidth();

      case DRAG_EDGE_TOP:
        return mRectMainClose.left;

      case DRAG_EDGE_BOTTOM:
        return mRectMainClose.left;

      default:
        return 0;
    }
  }

  private int getMainOpenTop() {
    switch (mDragEdge) {
      case DRAG_EDGE_LEFT:
        return mRectMainClose.top;

      case DRAG_EDGE_RIGHT:
        return mRectMainClose.top;

      case DRAG_EDGE_TOP:
        return mRectMainClose.top + mSecondaryView.getHeight();

      case DRAG_EDGE_BOTTOM:
        return mRectMainClose.top - mSecondaryView.getHeight();

      default:
        return 0;
    }
  }

  private int getSecOpenLeft() {
    if (mMode == MODE_NORMAL || mDragEdge == DRAG_EDGE_BOTTOM || mDragEdge == DRAG_EDGE_TOP) {
      return mRectSecClose.left;
    }

    if (mDragEdge == DRAG_EDGE_LEFT) {
      return mRectSecClose.left + mSecondaryView.getWidth();
    } else {
      return mRectSecClose.left - mSecondaryView.getWidth();
    }
  }

  private int getSecOpenTop() {
    if (mMode == MODE_NORMAL || mDragEdge == DRAG_EDGE_LEFT || mDragEdge == DRAG_EDGE_RIGHT) {
      return mRectSecClose.top;
    }

    if (mDragEdge == DRAG_EDGE_TOP) {
      return mRectSecClose.top + mSecondaryView.getHeight();
    } else {
      return mRectSecClose.top - mSecondaryView.getHeight();
    }
  }

  private void initRects() {
    // close position of main view
    mRectMainClose.set(mMainView.getLeft(), mMainView.getTop(), mMainView.getRight(),
        mMainView.getBottom());

    // close position of secondary view
    mRectSecClose.set(mSecondaryView.getLeft(), mSecondaryView.getTop(), mSecondaryView.getRight(),
        mSecondaryView.getBottom());

    // open position of the main view
    mRectMainOpen.set(getMainOpenLeft(), getMainOpenTop(), getMainOpenLeft() + mMainView.getWidth(),
        getMainOpenTop() + mMainView.getHeight());

    // open position of the secondary view
    mRectSecOpen.set(getSecOpenLeft(), getSecOpenTop(),
        getSecOpenLeft() + mSecondaryView.getWidth(), getSecOpenTop() + mSecondaryView.getHeight());
  }

  private void init(Context context, AttributeSet attrs) {
    if (attrs != null && context != null) {
      TypedArray a =
          context.getTheme().obtainStyledAttributes(attrs, R.styleable.SwipeRevealLayout, 0, 0);

      mDragEdge = a.getInteger(R.styleable.SwipeRevealLayout_dragEdge, DRAG_EDGE_LEFT);
      mMinFlingVelocity =
          a.getInteger(R.styleable.SwipeRevealLayout_flingVelocity, DEFAULT_MIN_FLING_VELOCITY);
      mMode = a.getInteger(R.styleable.SwipeRevealLayout_mode, MODE_NORMAL);

      mMinDistRequestDisallowParent =
          a.getDimensionPixelSize(R.styleable.SwipeRevealLayout_minDistRequestDisallowParent,
              dpToPx(DEFAULT_MIN_DIST_REQUEST_DISALLOW_PARENT));
    }

    mDragHelper = ViewDragHelper.create(this, 1.0f, mDragHelperCallback);
    mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);

    mGestureDetector = new GestureDetectorCompat(context, mGestureListener);
  }

  private final GestureDetector.OnGestureListener mGestureListener =
      new GestureDetector.SimpleOnGestureListener() {
        boolean hasDisallowed = false;

        @Override public boolean onDown(MotionEvent e) {
          mIsScrolling = false;
          hasDisallowed = false;
          return true;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
          mIsScrolling = true;
          return false;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
          mIsScrolling = true;

          if (getParent() != null) {
            boolean shouldDisallow;

            if (!hasDisallowed) {
              shouldDisallow = getDistToClosestEdge() >= mMinDistRequestDisallowParent;
              if (shouldDisallow) {
                hasDisallowed = true;
              }
            } else {
              shouldDisallow = true;
            }

            // disallow parent to intercept touch event so that the layout will work
            // properly on RecyclerView or view that handles scroll gesture.
            getParent().requestDisallowInterceptTouchEvent(shouldDisallow);
          }

          return false;
        }
      };

  private int getDistToClosestEdge() {
    switch (mDragEdge) {
      case DRAG_EDGE_LEFT:
        final int pivotRight = mRectMainClose.left + mSecondaryView.getWidth();

        return Math.min(mMainView.getLeft() - mRectMainClose.left,
            pivotRight - mMainView.getLeft());

      case DRAG_EDGE_RIGHT:
        final int pivotLeft = mRectMainClose.right - mSecondaryView.getWidth();

        return Math.min(mMainView.getRight() - pivotLeft,
            mRectMainClose.right - mMainView.getRight());

      case DRAG_EDGE_TOP:
        final int pivotBottom = mRectMainClose.top + mSecondaryView.getHeight();

        return Math.min(mMainView.getBottom() - pivotBottom, pivotBottom - mMainView.getTop());

      case DRAG_EDGE_BOTTOM:
        final int pivotTop = mRectMainClose.bottom - mSecondaryView.getHeight();

        return Math.min(mRectMainClose.bottom - mMainView.getBottom(),
            mMainView.getBottom() - pivotTop);
    }

    return 0;
  }

  private int getHalfwayPivotHorizontal() {
    if (mDragEdge == DRAG_EDGE_LEFT) {
      return mRectMainClose.left + mSecondaryView.getWidth() / 2;
    } else {
      return mRectMainClose.right - mSecondaryView.getWidth() / 2;
    }
  }

  private int getHalfwayPivotVertical() {
    if (mDragEdge == DRAG_EDGE_TOP) {
      return mRectMainClose.top + mSecondaryView.getHeight() / 2;
    } else {
      return mRectMainClose.bottom - mSecondaryView.getHeight() / 2;
    }
  }

  private final ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() {
    @Override public boolean tryCaptureView(View child, int pointerId) {
      mAborted = false;

      if (mLockDrag) return false;

      mDragHelper.captureChildView(mMainView, pointerId);
      return false;
    }

    @Override public int clampViewPositionVertical(View child, int top, int dy) {
      switch (mDragEdge) {
        case DRAG_EDGE_TOP:
          return Math.max(Math.min(top, mRectMainClose.top + mSecondaryView.getHeight()),
              mRectMainClose.top);

        case DRAG_EDGE_BOTTOM:
          return Math.max(Math.min(top, mRectMainClose.top),
              mRectMainClose.top - mSecondaryView.getHeight());

        default:
          return child.getTop();
      }
    }

    @Override public int clampViewPositionHorizontal(View child, int left, int dx) {
      switch (mDragEdge) {
        case DRAG_EDGE_RIGHT:
          return Math.max(Math.min(left, mRectMainClose.left),
              mRectMainClose.left - mSecondaryView.getWidth());

        case DRAG_EDGE_LEFT:
          return Math.max(Math.min(left, mRectMainClose.left + mSecondaryView.getWidth()),
              mRectMainClose.left);

        default:
          return child.getLeft();
      }
    }

    @Override public void onViewReleased(View releasedChild, float xvel, float yvel) {
      final boolean velRightExceeded = pxToDp((int) xvel) >= mMinFlingVelocity;
      final boolean velLeftExceeded = pxToDp((int) xvel) <= -mMinFlingVelocity;
      final boolean velUpExceeded = pxToDp((int) yvel) <= -mMinFlingVelocity;
      final boolean velDownExceeded = pxToDp((int) yvel) >= mMinFlingVelocity;

      final int pivotHorizontal = getHalfwayPivotHorizontal();
      final int pivotVertical = getHalfwayPivotVertical();

      switch (mDragEdge) {
        case DRAG_EDGE_RIGHT:
          if (velRightExceeded) {
            close(true);
          } else if (velLeftExceeded) {
            open(true);
          } else {
            if (mMainView.getRight() < pivotHorizontal) {
              open(true);
            } else {
              close(true);
            }
          }
          break;

        case DRAG_EDGE_LEFT:
          if (velRightExceeded) {
            open(true);
          } else if (velLeftExceeded) {
            close(true);
          } else {
            if (mMainView.getLeft() < pivotHorizontal) {
              close(true);
            } else {
              open(true);
            }
          }
          break;

        case DRAG_EDGE_TOP:
          if (velUpExceeded) {
            close(true);
          } else if (velDownExceeded) {
            open(true);
          } else {
            if (mMainView.getTop() < pivotVertical) {
              close(true);
            } else {
              open(true);
            }
          }
          break;

        case DRAG_EDGE_BOTTOM:
          if (velUpExceeded) {
            open(true);
          } else if (velDownExceeded) {
            close(true);
          } else {
            if (mMainView.getBottom() < pivotVertical) {
              open(true);
            } else {
              close(true);
            }
          }
          break;
      }
    }

    @Override public void onEdgeDragStarted(int edgeFlags, int pointerId) {
      super.onEdgeDragStarted(edgeFlags, pointerId);

      if (mLockDrag) {
        return;
      }

      boolean edgeStartLeft =
          (mDragEdge == DRAG_EDGE_RIGHT) && edgeFlags == ViewDragHelper.EDGE_LEFT;

      boolean edgeStartRight =
          (mDragEdge == DRAG_EDGE_LEFT) && edgeFlags == ViewDragHelper.EDGE_RIGHT;

      boolean edgeStartTop =
          (mDragEdge == DRAG_EDGE_BOTTOM) && edgeFlags == ViewDragHelper.EDGE_TOP;

      boolean edgeStartBottom =
          (mDragEdge == DRAG_EDGE_TOP) && edgeFlags == ViewDragHelper.EDGE_BOTTOM;

      if (edgeStartLeft || edgeStartRight || edgeStartTop || edgeStartBottom) {
        mDragHelper.captureChildView(mMainView, pointerId);
      }
    }

    @Override
    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
      super.onViewPositionChanged(changedView, left, top, dx, dy);
      if (mMode == MODE_SAME_LEVEL) {
        if (mDragEdge == DRAG_EDGE_LEFT || mDragEdge == DRAG_EDGE_RIGHT) {
          mSecondaryView.offsetLeftAndRight(dx);
        } else {
          mSecondaryView.offsetTopAndBottom(dy);
        }
      }

      boolean isMoved =
          (mMainView.getLeft() != mLastMainLeft) || (mMainView.getTop() != mLastMainTop);
      if (mSwipeListener != null && isMoved) {
        if (mMainView.getLeft() == mRectMainClose.left
            && mMainView.getTop() == mRectMainClose.top) {
          mSwipeListener.onClosed(SwipeRevealLayout.this);
        } else if (mMainView.getLeft() == mRectMainOpen.left
            && mMainView.getTop() == mRectMainOpen.top) {
          mSwipeListener.onOpened(SwipeRevealLayout.this);
        } else {
          mSwipeListener.onSlide(SwipeRevealLayout.this, getSlideOffset());
        }
      }

      mLastMainLeft = mMainView.getLeft();
      mLastMainTop = mMainView.getTop();
      ViewCompat.postInvalidateOnAnimation(SwipeRevealLayout.this);
    }

    private float getSlideOffset() {
      switch (mDragEdge) {
        case DRAG_EDGE_LEFT:
          return (float) (mMainView.getLeft() - mRectMainClose.left) / mSecondaryView.getWidth();

        case DRAG_EDGE_RIGHT:
          return (float) (mRectMainClose.left - mMainView.getLeft()) / mSecondaryView.getWidth();

        case DRAG_EDGE_TOP:
          return (float) (mMainView.getTop() - mRectMainClose.top) / mSecondaryView.getHeight();

        case DRAG_EDGE_BOTTOM:
          return (float) (mRectMainClose.top - mMainView.getTop()) / mSecondaryView.getHeight();

        default:
          return 0;
      }
    }

    @Override public void onViewDragStateChanged(int state) {
      super.onViewDragStateChanged(state);
      final int prevState = mState;

      switch (state) {
        case ViewDragHelper.STATE_DRAGGING:
          mState = STATE_DRAGGING;
          break;

        case ViewDragHelper.STATE_IDLE:

          // drag edge is left or right
          if (mDragEdge == DRAG_EDGE_LEFT || mDragEdge == DRAG_EDGE_RIGHT) {
            if (mMainView.getLeft() == mRectMainClose.left) {
              mState = STATE_CLOSE;
            } else {
              mState = STATE_OPEN;
            }
          }

          // drag edge is top or bottom
          else {
            if (mMainView.getTop() == mRectMainClose.top) {
              mState = STATE_CLOSE;
            } else {
              mState = STATE_OPEN;
            }
          }
          break;
      }
    }
  };

  private int pxToDp(int px) {
    Resources resources = getContext().getResources();
    DisplayMetrics metrics = resources.getDisplayMetrics();
    return (int) (px / ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT));
  }

  private int dpToPx(int dp) {
    Resources resources = getContext().getResources();
    DisplayMetrics metrics = resources.getDisplayMetrics();
    return (int) (dp * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT));
  }
}

В результате мы добавили в список кнопку, которая появляется при свайпе элемента и позволяет либо добавить сеть в архив, либо удалить её.

 

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *