Поиск по этому блогу

понедельник, 31 июля 2017 г.

Закрытии активити свайпом в право как в Telegram

Многие пользователи телеграма я думаю задумывались как же они сделали эту удобную фичу с сдвигиванием экрана вправо для закрытия текущей активити? Я думаю все, ибо это очень удобно, ну прям вообще супер удобно, почему это не ввели в android'e как и в iOS по дефолту я не понимаю, ведь эта фича по сути не так и сложная в реализации и она очень интуитивна и проста в использовании, не надо тянутся каждый раз к кнопке назад в левом верхнем углу, и не надо тянуться к кнопкам в низу экрана, просто смахиваешь в право.


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

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


app/build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.project.swipetocloseactivity"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

repositories {
    jcenter()
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.r0adkll:slidableactivity:2.0.5'
}

Здесь у нас из добавленного это пакет repositories в котором мы указали откуда нам подтягивать файлы библиотеки, и в dependencies — r0adkll:slidableactivity, вот собственно и все что нам нужно добавить. 

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

anim/slide_in_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="100%p" android:toXDelta="0"
        android:duration="@android:integer/config_shortAnimTime"/>
</set>

Как видно мы двигаем по X c права на лево, как бы перекрывая второй активити поверх первой, так как правая дельта, это android:toXDelta = 0. Эту анимацию мы будем использовать для открытия активити. А скорость анимации мы устанавливаем в android:duration, в нашем случае она стоит самая быстрая.

anim/slide_out_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="0" android:toXDelta="100%p"
        android:duration="@android:integer/config_shortAnimTime"/>
</set>

А этот кусок анимации сдвигает с лева на право, ее мы будем использовать для закрытия. Здесь как вы видите мы наоборот сделали android:fromXDelta = 0 то есть двигать будем теперь не с права на лево, а наоборот.

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

styles.xml
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
       
        <!-- Вот этот кусок который делает активити прозрачной. -->
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
    </style>

</resources>

Вот прокомментированная часть как раз и есть тем что нам нужно. Вот подготовка закончилась. Теперь нам осталось создать пару активити и бейз класс для них и все, будет готовая красота.

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

BaseSwipeToDismissActivity.java
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

import com.r0adkll.slidr.Slidr;

public abstract class BaseSwipeToDismissActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutId());
        if(isActivityDraggable())
            Slidr.attach(this);
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        if(isActivityDraggable())
            overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_right);
    }

    public void startActivity(Context context, Class<?> className) {
        startActivity(new Intent(context, className));
        overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_right);
    }

    public abstract int getLayoutId();
    public abstract boolean isActivityDraggable();
}

И так что же у нас тут. Для начала мы переопределили в этом классе метод onCreate(), что бы не писать по сотне раз в активити один и тот же код, проще создать один раз в родительском классе этот метод и просто переопределить нужные поля в абстрактных методах. Что я собственно тут и сделал. Я создал метод getLayoutId() который передает в setContentView() — ID леяута который мы хотим отобразить. А метод isActivityDraggable() возвращает состояние активити, надо ли нам ее делать доступной для свайпа или нет, на родительских активити за которыми нету уже других активити я просто убираю эту фичу, и она не закрывается свайпом. 

Собственно строчка Slidr.attach(this); и создает эту функцию для свайпа с права на лево для финиша активити. 

Так же я переопределил метод onBackPressed(), в нем я переопределил его же super метод, и добавил проверку надо ли нам свайпить активити анимацией или же закрывать стандартной анимацией.

Переопределил метод startActivity(), она у нас принимает контекст и принимает класс на который перейти, и добавляет анимацию перехода. 

Ну и собственно два абстрактных метода которые у нас имплементятся в активити после наследия этого класса вместо стандартной AppCompatActivity.

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

MainActivity.java
import android.view.View;

public class MainActivity extends BaseSwipeToDismissActivity {

    @Override
    public int getLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    public boolean isActivityDraggable() {
        return false;
    }

    public void onClick(View view) {
        startActivity(this, SecondActivity.class);
    }
}

В getLayoutId() передали леяут который хотим отобразить. isActivityDraggable() у нас false так как не хотим что бы эта активити свайпалась. onClick() сделали для клика на кнопку.

Вот так будет выглядеть леяут для нашей активити. Там всего лишь одна кнопка.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:gravity="center_vertical|center_horizontal"
    android:orientation="vertical"
    tools:context="com.project.swipetocloseactivity.MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="Push me to go" />

</LinearLayout>

И этой кнопке присвоен android:onClick = onClick метод для отслеживания клика.

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

SecondActivity.java
public class SecondActivity extends BaseSwipeToDismissActivity {

    @Override
    public int getLayoutId() {
        return R.layout.activity_second;
    }

    @Override
    public boolean isActivityDraggable() {
        return true;
    }
}

В ней у нас опять же только два наших абстрактных метода из бейз класса. В getLayoutId() передали леяут который отображаем. А isActivityDraggable() мы сделали true так как хотим что бы эта активити свайпилась.

activity_second.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical|center_horizontal"
    android:background="@android:color/white">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello!"
        android:textSize="30sp" />

</LinearLayout>

Просто текствьюха с текстом Hello!.. Этого думаю достаточно для примера.

Ну и не забываем еще добавить активити в AndroidManifest, а то будем падать как черти.

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.project.swipetocloseactivity">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".SecondActivity" />
    </application>

</manifest>

Вот собственно и все. Дальше как всегда пробуем скомпилировать, должно получится как на видео выше. Буду надеятся что кому-то это пригодится, не только мне :)

Исходники:
GitHub