воскресенье, 23 июля 2017 г.

Создание горизонтальной пошаговой вьюхи со списком относящимся к шагам

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

 


Код особо не претендует на свою гениальность, я когда это писал, плакал как девочка ибо проект то написан именно таким макаром, и эти действия все можно в принципе реализовать немного иначе, даже с помощью того-же RecyclerView разделив его на две части, первая была бы горизонтальной вьюхой, а вторая была бы списком, но в моем случае я сделал отдельными вьюхами. Первая это кастомная вью с HorizontalScrollView, а вторая это ListView в которой я по ID отображаю нужный мне список данных. Данные же я хранить буду в БД которую я создам с помощью Realm. Про него я кстати уже писал, можете почитать статью, возможно окажется полезной…

Для начала давайте настроим проект перед стартом. Я буду использовать две библиотеки для созданния данного проекта, первая это Realm — для создания БД и вторая ButterKnife — для удобного подключения вьюх в проекте. Вот так будет выглядеть мой build.gradle:

build.gradle

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

Тут мы подключили зависимость для ButterKnife — com.neenbedankt.gradle.plugins:android-apt:1.8', а так он остается по сути такой же каким его создает Android Studio. Дальше у нас надо добавить библиотеки в app/build.gradle.

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

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

repositories { mavenCentral() }

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

    compile 'io.realm:realm-android:0.87.+'
    compile 'com.jakewharton:butterknife:8.0.1'
    apt 'com.jakewharton:butterknife-compiler:8.0.1'
}

Мы добавили плагин для подключения библиотек через приставку apt — android-apt и потом аж в dependencies мы добавили три строчки для подключения Realm'a и ButterKnife.

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

Сперва покажу как будет выглядеть xml часть нашей вьюхи.

view_steps_button.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="200dp"
    android:gravity="center_vertical"
    android:orientation="horizontal">

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="200dp"
        android:layout_gravity="center_vertical"
        android:gravity="center_vertical">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:orientation="horizontal">

            <View
                android:id="@+id/lineView"
                android:layout_width="@dimen/lineNormalSize"
                android:layout_height="3dp"
                android:layout_marginTop="-20dp"
                android:background="@color/white"></View>

        </LinearLayout>

        <LinearLayout
            android:id="@+id/layout"
            android:layout_width="wrap_content"
            android:layout_height="200dp"
            android:layout_alignParentEnd="false"
            android:layout_alignParentRight="false"
            android:layout_alignParentTop="true"
            android:layout_marginLeft="35dp"
            android:gravity="center_vertical|center_horizontal"
            android:orientation="vertical">

            <Button
                android:id="@+id/mainButton"
                android:layout_width="@dimen/mainButtonSize"
                android:layout_height="@dimen/mainButtonSize"
                android:background="@drawable/round_button"
                android:text="1"
                android:textColor="@color/colorPrimary"
                android:textSize="@dimen/smallButtonSize" />

            <Button
                android:id="@+id/subButton"
                android:layout_width="@dimen/smallButtonSize"
                android:layout_height="@dimen/smallButtonSize"
                android:background="@drawable/round_button"
                android:text="1.1"
                android:textColor="@color/colorPrimary"
                android:textSize="@dimen/smallButtonTextSize"
                android:visibility="invisible" />
        </LinearLayout>

    </RelativeLayout>

</LinearLayout>

Здесь мы создали две кнопки, первая у нас видимая которая главная и которая побольше, вторая которая находится под ней, она невидимая и она соответственно меньшего размера. Еще у нас там есть полоска в три пикселя, она находится по центру вьюхи с сдвигом в -20 пикселей, что бы находится всегда по центру именно главной, большой кнопки, так как у нас вся вьюха строится вокруг именно ее.

Еще у нас к нашим кнопкам идет стиль что бы эти кнопки сделать круглыми. Вот он как выглядит.

round_button.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item>
        <shape android:shape="rectangle" >
        </shape>
    </item>
    <item>
        <shape
            android:innerRadiusRatio="4"
            android:shape="oval"
            android:thicknessRatio="9"
            android:useLevel="false" >
            <solid android:color="@color/white" />
        </shape>
    </item>

</layer-list>

Здесь мы просто прописываем что кнопка у нас будет в форме овала, белого цвета 

ButtonView.java
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

import com.app.horizontalsteps.R;

import butterknife.BindDimen;
import butterknife.BindView;
import butterknife.ButterKnife;

public class ButtonView extends LinearLayout {

    @BindView(R.id.mainButton)
    Button mainButton;
    @BindView(R.id.subButton)
    Button subButton;
    @BindView(R.id.lineView)
    View lineView;

    @BindDimen(R.dimen.mainButtonSize)
    int mainButtonSize;
    @BindDimen(R.dimen.mainButtonTextSize)
    int mainButtonTextSize;
    @BindDimen(R.dimen.smallButtonSize)
    int smallButtonSize;
    @BindDimen(R.dimen.smallButtonTextSize)
    int smallButtonTextSize;
    @BindDimen(R.dimen.buttonPadding)
    int buttonPadding;
    @BindDimen(R.dimen.bigSubButtonPaddingBottom)
    int bigSubButtonPadding;
    @BindDimen(R.dimen.lineLongSize)
    int lineLongSize;
    @BindDimen(R.dimen.lineNormalSize)
    int lineNormalSize;
    @BindDimen(R.dimen.lineHeight)
    int lineHeight;
    @BindDimen(R.dimen.paddingOfLine)
    int paddingOfLine;

    private LinearLayout.LayoutParams smallSize;
    private LinearLayout.LayoutParams bigSize;
    private LinearLayout.LayoutParams bigSubButton;

    public ButtonView(Context context) {
        super(context);
        init(context);
    }

    public ButtonView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ButtonView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        inflate(context, R.layout.view_steps_button, this);
        setOrientation(HORIZONTAL);
        ButterKnife.bind(this);
        setupView();
    }

    private void setupView() {
        bigSize = new LinearLayout.LayoutParams(mainButtonSize, mainButtonSize);
        bigSize.setMargins(0, 0, 0, buttonPadding);
        smallSize = new LinearLayout.LayoutParams(smallButtonSize, smallButtonSize);
        smallSize.setMargins(0, 0, 0, buttonPadding);
        mainButton.setLayoutParams(bigSize);

        bigSubButton = new LinearLayout.LayoutParams(mainButtonSize, mainButtonSize);
        bigSubButton.setMargins(0, 0, 0, bigSubButtonPadding);
    }

    public Button getMainButton() {
        return mainButton;
    }

    public Button getSubButton() {
        return subButton;
    }

    public View getLineView() {
        return lineView;
    }

    public LayoutParams getSmallButtonSizeStyle() {
        return smallSize;
    }

    public LayoutParams getBigButtonSizeStyle() {
        return bigSize;
    }

    public LayoutParams getBigSubButton() {
        return bigSubButton;
    }

    public LayoutParams getLineNormalSize() {
        LayoutParams params = new LayoutParams(lineNormalSize, lineHeight);
        params.setMargins(0, paddingOfLine, 0, 0);
        return params;
    }

    public LayoutParams getLineLongSize() {
        LayoutParams params = new LayoutParams(lineLongSize, lineHeight);
        params.setMargins(0, paddingOfLine, 0, 0);
        return params;
    }

    public int smallTextSize() {
        return smallButtonTextSize;
    }

    public int textSize() {
        return mainButtonTextSize;
    }
}

Здесь мы проинициализировали наши кнопки, создали пару стилей. Вот на пример в методе init() мы подключили ButterKnife и прицепили нашу вьюху. Дальше в методе setupView() мы задали стили для большой кнопки и для маленькой кнопке. Так же в этом классе мы проинициализировали еще кучу dimens которые нам нужны для изменения кнопок и текста на них и размера линий которые соединяют наши круглые кнопки, так как если мы не будем этого делать, то у нас постоянно будет дырка между ними.

А еще нам не хватает в файле dimens.xml наших настроек которые мы указали выше. Вот так он будет у нас выглядеть.

dimens.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <dimen name="smallButtonSize">30dp</dimen>
    <dimen name="smallButtonTextSize">4sp</dimen>
    <dimen name="mainButtonSize">75dp</dimen>
    <dimen name="mainButtonTextSize">10sp</dimen>

    <dimen name="lineNormalSize">35dp</dimen>
    <dimen name="lineLongSize">110dp</dimen>
    <dimen name="lineHeight">3dp</dimen>

    <dimen name="buttonPadding">5dp</dimen>

    <dimen name="bigSubButtonPaddingBottom">-40dp</dimen>
    <dimen name="paddingOfLine">-20sp</dimen>
    <dimen name="buttonPaddingOffset">150dp</dimen>

</resources>

Теперь нам нужно создать главную вьюху которая будет в себе содержать HorizontalScrollView в которую мы будем сетить нашу ButtonView. 

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

view_buttons_step.xml
<?xml version="1.0" encoding="utf-8"?>
<com.app.horizontalsteps.ui.view.ObservableHorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/horizontalScrollView"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:background="@color/colorPrimary"
    android:fillViewport="true"
    android:scrollbars="none">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal">

        <LinearLayout
            android:id="@+id/buttonsView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_above="@+id/textView27"
            android:layout_alignParentTop="true"
            android:gravity="center_vertical|center_horizontal"
            android:orientation="horizontal">

        </LinearLayout>

    </LinearLayout>
</com.app.horizontalsteps.ui.view.ObservableHorizontalScrollView>

Тут как видно все просто, у нас есть сам горизонтальный скролл и в нем пара LinearLayout'ов. В тот который имеет id — buttonsView мы будем сетить наш класс ButtonView. Думаю что вы заметили что мы используем кастомный HorizontalScrollView, в нем у нас тоже происходит некоторая магия, давайте покажу какая.

ObservableHorizontalScrollView.java
import android.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;

import com.app.horizontalsteps.R;

public class ObservableHorizontalScrollView  extends HorizontalScrollView implements ViewTreeObserver.OnPreDrawListener {

    public ObservableHorizontalScrollView(Context context) {
        super(context);
        setup();
    }

    public ObservableHorizontalScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setup();
    }

    private void setup() {
        getViewTreeObserver().addOnPreDrawListener(this);
    }

    @Override
    public boolean onPreDraw() {
        getViewTreeObserver().removeOnPreDrawListener(this);

        LinearLayout child = (LinearLayout) getChildAt(0);

        int width = getWidth();
        LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(width / 2, LinearLayout.LayoutParams.MATCH_PARENT);

        View leftSpacer = new View(getContext());
        leftSpacer.setLayoutParams(p);
        child.addView(leftSpacer, 0);

        View rightSpacer = new View(getContext());
        rightSpacer.setLayoutParams(p);
        child.addView(rightSpacer);

        return false;
    }

    public void scrollToTheEnd() {
        postDelayed(new Runnable() {
            public void run() {
                fullScroll(HorizontalScrollView.FOCUS_RIGHT);
            }
        }, 100L);
    }

    public void scrollToView(final View view) {
        postDelayed(new Runnable() {
            public void run() {
                int dim = (int) getContext().getResources().getDimension(R.dimen.buttonPaddingOffset);
                Point childOffset = new Point();
                getDeepChildOffset(view.getParent(), view, childOffset);
                smoothScrollTo(childOffset.x - dim, 0);
            }
        }, 100L);
    }

    private void getDeepChildOffset(ViewParent parent, View child, Point accumulatedOffset) {
        ViewGroup parentGroup = (ViewGroup) parent;
        accumulatedOffset.x += child.getLeft();
        if (parentGroup.equals(this)) {
            return;
        }
        getDeepChildOffset(parentGroup.getParent(), parentGroup, accumulatedOffset);
    }
}

Для того что бы у нас наш scrollView умел отображать айтемы внутри скролла по центру, то есть что бы мы могли дотянуть самый крайний левый и самый крайний правый айтемы в центр scrollView, в обычном мы так сделать не сможем, по этому пришлось выпендриваться и добавлять по отступу по бокам с права и с лева. Это все происходит тут onPreDraw(). 

Так же у нас есть метод scrollToTheEnd() — его мы будем использовать для скролла тупо в самый конец scrollView.
scrollToView() — этот метод мы будем использовать для центрирования кликнутой вьюхи.
getDeepChildOffset() — просто метод который расчитывает координаты кликнутой вьюхи и передает их в scrollToView().

Дальше давайте посмотрим как будет выглядеть класс ButtonStepsView и я раздельно по методам раскажу как тут все работает.

ButtonStepsView.java
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.app.horizontalsteps.R;

import java.util.ArrayList;
import java.util.List;

import butterknife.BindView;
import butterknife.ButterKnife;

import static com.app.horizontalsteps.R.id.horizontalScrollView;

public class ButtonStepsView extends LinearLayout {

    @BindView(R.id.buttonsView)
    LinearLayout buttonsView;
    @BindView(horizontalScrollView)
    ObservableHorizontalScrollView scrollView;

    private ButtonView buttonView;

    private int selectedStep = 0;
    private int buttonNumberCounter = 0;
    private String stepNumber = "1";
    private Context context;

    private Listener listener;

    private List<Button> allMainBtns;
    private List<Button> allSubBtns;
    private List<View> allLinesViews;
    private List<ButtonView> buttonViewList;

    public ButtonStepsView(Context context) {
        super(context);
        init(context);
    }

    public ButtonStepsView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ButtonStepsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public void init(Context context) {
        this.context = context;
        inflate(context, R.layout.view_buttons_steps, this);
        setOrientation(VERTICAL);
        ButterKnife.bind(this);

        allMainBtns = new ArrayList<>();
        allSubBtns = new ArrayList<>();
        allLinesViews = new ArrayList<>();
        buttonViewList = new ArrayList<>();
    }

    public void addMainButton() {
        listener.onUpdateAdapter();
        buttonNumberCounter++;
        selectedStep = buttonNumberCounter - 1;
        stepNumber = String.valueOf(buttonNumberCounter);

        buttonView = new ButtonView(context);
        buttonView.getMainButton().setId(buttonNumberCounter);
        buttonView.getMainButton().setText(stepNumber);
        buttonView.getMainButton().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onButtonClick(v, allMainBtns);
            }
        });
        buttonsView.addView(buttonView);

        allMainBtns.add(buttonView.getMainButton());
        allSubBtns.add(buttonView.getSubButton());
        allLinesViews.add(buttonView.getLineView());
        buttonViewList.add(buttonView);

        makeAllButtonsSmall(buttonView, allMainBtns);
        scrollView.scrollToTheEnd();
    }

    public void addSubBtn() {
        listener.onUpdateAdapter();
        stepNumber = String.valueOf(String.format("%d.1", selectedStep + 1));

        buttonViewList.get(selectedStep).getSubButton().setVisibility(View.VISIBLE);
        buttonViewList.get(selectedStep).getSubButton().setText(stepNumber);
        buttonViewList.get(selectedStep).getSubButton().setId(selectedStep);
        buttonViewList.get(selectedStep).getSubButton().setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                onButtonClick(view, allSubBtns);
            }
        });
        makeAllButtonsSmall(buttonViewList.get(selectedStep).getSubButton(), allSubBtns);
    }

    private void onButtonClick(final View v, final List<Button> buttons) {
        stepNumber = ((TextView) v).getText().toString();
        selectedStep = v.getId() - 1;

        makeAllButtonsSmall(v, null);
        if(buttons == allSubBtns) {
            buttons.get(selectedStep + 1).setLayoutParams(buttonView.getBigSubButton());
            allLinesViews.get(selectedStep + 1).setLayoutParams(buttonView.getLineLongSize());
            buttons.get(selectedStep + 1).setTextSize(buttonView.textSize());
            scrollView.scrollToView(buttons.get(selectedStep + 1));
        } else {
            buttons.get(selectedStep).setLayoutParams(buttonView.getBigButtonSizeStyle());
            allLinesViews.get(selectedStep).setLayoutParams(buttonView.getLineNormalSize());
            buttons.get(selectedStep).setTextSize(buttonView.textSize());
            scrollView.scrollToView(buttons.get(selectedStep));
        }
        listener.selectData();
    }

    private void makeAllButtonsSmall(View v, List<Button> clickedButtons) {
        setButtonsStyle(allMainBtns);
        setButtonsStyle(allSubBtns);
        //instead of one which was clicked
        if(clickedButtons != null) {
            int id = v.getId();
            if(clickedButtons == allSubBtns) {
                clickedButtons.get(id).setLayoutParams(buttonView.getBigSubButton());
                allLinesViews.get(id).setLayoutParams(buttonView.getLineLongSize());
                clickedButtons.get(id).setTextSize(buttonView.textSize());
            } else {
                clickedButtons.get(clickedButtons.size() - 1).setLayoutParams(buttonView.getBigButtonSizeStyle());
                allLinesViews.get(clickedButtons.size() - 1).setLayoutParams(buttonView.getLineNormalSize());
                clickedButtons.get(clickedButtons.size() - 1).setTextSize(buttonView.textSize());
            }
        }
    }

    private void setButtonsStyle(List<Button> buttons) {
        for (int i = 0; i < buttons.size(); i++) {
            buttons.get(i).setLayoutParams(buttonView.getSmallButtonSizeStyle());
            buttons.get(i).setTextSize(buttonView.smallTextSize());
            allLinesViews.get(i).setLayoutParams(buttonView.getLineNormalSize());
        }
    }

    public String getStepNumber() {
        return stepNumber;
    }

    public void setListener(Listener listener) {
        this.listener = listener;
    }

    public interface Listener {
        void onUpdateAdapter();
        void selectData();
    }
}

В шапке этого класса мы инициализиуем список, леяут в который будем сетить кнопки, создаем объект класса ButtonView и создаем несколько списков для сохранения состояний тех или иных вьюх.
В методе init() мы как всегда инициализируем ButterKnife и цепляем леяут к вьюхе.
addMainButton() — в этом методе мы создаем кнопку с ее параметрами, лисенарами и добавляем все это в списки для того что бы мы могли оперировать далее ими. По созданию кнопки мы делаем все кнопки маленькими, а ту которую создали мы делаем большой и вызываем метод скролла в самый конец scrollView().
addSubBtn() — его мы вызываем когда хотим сделать видимой саб кнопку которая у нас находится под главной кнопкой. И опять же, в ней мы делаем все кнопки маленького размера, а саб кнопку мы делаем большой.
onButtonClick() — метод который по клику делает все кнопки маленькими, а кликнутую мы делаем большой и переносим нас к ней по центру. Коллбек в конце метода обновляет список который мы будем отображать под вьюхой.
makeAllButtonsSmall() — общий метод который делает все кнопки маленькими, а нужную делает больше. Его мы вызываем постоянно когда нам нужно в вьюхе сделать кнопки меньше, а на определенной сконцентрировать и сделать ее больше.
setButtonsStyle() — метод который делает все кнопки маленькими, оба метода makeAllButtonsSmall() и setButtonsStyle() завязаны друг на друге. 

Дальше у нас идут геттеры и сеттеры которые мы используем для получения номера шага и сета лисенера коллбека. Коллбек же умеет обновлять данные в адаптере и выводить нужные данные в него.

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

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

StepsActivity.java
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

import com.app.horizontalsteps.R;

public class StepsActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_steps);
    }
}

Пока все довольно просто выглядит :) А еще у нас есть activity_steps в котором мы наш фрагмент указываем как default.

activity_steps.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <fragment
        android:id="@+id/add"
        android:name="com.app.horizontalsteps.ui.StepsFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:layout="@layout/fragment_steps"
        android:layout_weight="0.1" />

</LinearLayout>

Просто указываем в теге fragment что мы хотим что бы наш StepsFragment будет дефолтным и нам нужно в этой активити отображать только его.

Теперь рассмотрим разметку нашего фрагмента.

fragment_steps.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:background="@color/white"
    android:orientation="vertical"
    android:weightSum="1">

    <com.app.horizontalsteps.ui.view.ButtonStepsView
        android:id="@+id/buttonsStepsView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="10dp">

        <TextView
            android:id="@+id/textView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Items"
            android:textSize="18sp" />

        <ListView
            android:id="@+id/listView2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="0.1" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_above="@+id/seekBarLayout"
            android:layout_alignParentBottom="false"
            android:layout_alignParentEnd="true"
            android:layout_alignParentRight="true"
            android:gravity="center_horizontal"
            android:orientation="horizontal">

            <Button
                android:id="@+id/nextBtn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:background="@color/colorPrimary"
                android:onClick="onNextClick"
                android:text="CREATE STEP"
                android:textColor="@color/white" />

            <Button
                android:id="@+id/addBtn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:layout_weight="1"
                android:background="@color/colorPrimary"
                android:onClick="onAddClick"
                android:text="ADD ITEM"
                android:textColor="@color/white" />

        </LinearLayout>
    </LinearLayout>

</LinearLayout>

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

StepsFragment.java
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;

import com.app.horizontalsteps.CreateStepActivity;
import com.app.horizontalsteps.R;
import com.app.horizontalsteps.ui.adapter.StepsAdapter;
import com.app.horizontalsteps.ui.db.StepsController;
import com.app.horizontalsteps.ui.db.items.StepsDataIModel;
import com.app.horizontalsteps.ui.view.ButtonStepsView;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import io.realm.RealmResults;

public class StepsFragment extends Fragment implements ButtonStepsView.Listener {

    private static final int RESULT_CODE_CREATE_STEP = 3;

    @BindView(R.id.listView2)
    ListView listView;
    @BindView(R.id.buttonsStepsView)
    ButtonStepsView buttonStepsView;

    private StepsAdapter adapter;
    private RealmResults<StepsDataIModel> recData;

    private int itemsCount = 0;

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        recData = new StepsController(getActivity()).getInfo(buttonStepsView.getStepNumber());
        adapter = new StepsAdapter(getActivity(), recData);
        listView.setAdapter(adapter);

        buttonStepsView.setListener(this);
        buttonStepsView.addMainButton();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_steps, container, false);
        ButterKnife.bind(this, rootView);
        return rootView;
    }

    @OnClick(R.id.nextBtn)
    public void onNextClick() {
        itemsCount = 0;

        Intent i = new Intent(getActivity(), CreateStepActivity.class);
        startActivityForResult(i, RESULT_CODE_CREATE_STEP);
    }

    @OnClick(R.id.addBtn)
    public void onAddClick() {
        itemsCount++;

        String fileName = "Item_" + String.format("%03d", itemsCount);
        new StepsController(getActivity()).addInfo(buttonStepsView.getStepNumber(), fileName);
        selectData();
    }

    @Override
    public void selectData() {
        recData = new StepsController(getActivity()).getInfo(buttonStepsView.getStepNumber());
        if(recData.size() != 0) {
            adapter = new StepsAdapter(getActivity(), recData);
            listView.setAdapter(adapter);
        } else
            listView.setAdapter(null);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case RESULT_CODE_CREATE_STEP:
                if (resultCode == Activity.RESULT_OK) {
                    if(resultCode == getActivity().RESULT_OK) {
                        String result = data.getStringExtra(CreateStepActivity.RESULT);
                        if(result.equals(CreateStepActivity.RESULT_SECTION)) {
                            buttonStepsView.addMainButton();
                        } else if(result.equals(CreateStepActivity.RESULT_UNDER_TOUR)) {
                            buttonStepsView.addSubBtn();
                        }
                    }
                }
            break;
        }
    }

    @Override
    public void onUpdateAdapter() {
        listView.setAdapter(null);
    }
}

В шапке класса у нас как всегда идет инициализация всех нужных переменных и констант. А вот методы я опишу что в них да как.
onViewCreated() — в нем мы создали нужные нам объекты адаптера, списка и вьюх и заодно создали первую кнопку которая у нас должна быть на экране.
onCreateView() — по старинке сетит леяут в фрагмент.
onNextClick() — отслеживает клик по кнопке Create step.
onAddClick() — добавляет каждый клик в наш ListView один айтем.
selectData() — создает адаптер и сетит в него данные.
onActivityResult() — как только возвращается какой-то результат оно в зависимости от того что вернула активити создает или главную кнопку или саб кнопку.
onUpdateAdapter() — метод который обнуляет адаптер когда создается новая кнопка.

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

Начнем мы с того что создадим адаптер для отображения айтемов. Он простой до ужаса.

StepsAdapter.java
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.app.horizontalsteps.R;
import com.app.horizontalsteps.ui.db.items.StepsDataIModel;

import butterknife.BindView;
import butterknife.ButterKnife;
import io.realm.RealmResults;

public class StepsAdapter extends BaseAdapter {

    private Context context;
    private RealmResults<StepsDataIModel> data = null;

    public StepsAdapter(Context context, RealmResults<StepsDataIModel> data) {
        this.context = context;
        this.data = data;
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int position) {
        return data.get(position);
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        LayoutInflater inflater = ((Activity)context).getLayoutInflater();
        convertView = inflater.inflate(R.layout.item_steps, parent, false);

        Holder holder = new Holder(convertView);

        StepsDataIModel rec = data.get(position);
        holder.name.setText(rec.getName());

        return convertView;
    }

    class Holder {
        @BindView(R.id.nameText)
        TextView name;

        public Holder(View view) {
            ButterKnife.bind(this, view);
        }
    }
}

В этот адаптер мы сетим данные из нашего контроллера, они у нас находятся в виде RealmResults. Если что этот формат можно переконвертировать в в ArrayList или в String[]. В общем в любой удобный вид. Далее мы в getView() сетим данные из этого RealmResult класса и выводим в текствью. А вот так будет выглядеть наш леяут для айтемов в адаптере.

item_steps.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="70dp"
    android:gravity="center_vertical">

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="false"
        android:layout_marginLeft="5dp"
        android:layout_marginTop="5dp"
        android:layout_marginRight="5dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Large Text"
            android:id="@+id/nameText"
            android:layout_alignParentTop="true"
            android:layout_toRightOf="@+id/radio"
            android:layout_toEndOf="@+id/radio"
            android:gravity="center_vertical"
            android:layout_marginLeft="10dp" />

        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/radio"
            android:layout_alignBottom="@+id/nameText"
            android:layout_alignParentTop="true"
            android:clickable="false"
            android:enabled="true" />

    </RelativeLayout>

</RelativeLayout>

Еще нам не хватает контроллера для БД. В нем у нас всего пара методов, для загрузки данных и выгрузки, нам этого хватит.

StepsController.java
import android.content.Context;

import com.app.horizontalsteps.ui.db.items.StepsDataIModel;

import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.RealmResults;

public class StepsController {

    private Realm realm;

    public StepsController(Context context) {
        RealmConfiguration config = new RealmConfiguration.Builder(context).build();
        realm.setDefaultConfiguration(config);
        realm = Realm.getDefaultInstance();
    }

    public void addInfo(String stepID, String name) {
        realm.beginTransaction();

        StepsDataIModel realmObject = realm.createObject(StepsDataIModel.class);
        int id = getNextKey();
        realmObject.setId(id);
        realmObject.setStepID(stepID);
        realmObject.setName(name);

        realm.commitTransaction();
    }

    public RealmResults<StepsDataIModel> getInfo(String stepId) {
        return realm.where(StepsDataIModel.class).equalTo("stepID", stepId).findAll();
    }

    private int getNextKey() {
        return realm.where(StepsDataIModel.class).max("id").intValue() + 1;
    }
}

В конструкторе мы проинициализировали Realm, задали конфиги и т.д. А дальше мы создали два метода, первый это addInfo() который записывает данные в БД. Второй это getInfo(), в нем мы вытаскиваем все данные из БД по ID и выводим в RealmResults. Еще есть методв getNextKey() но он нужен для того что бы в БД были уникальные айдишники, чисто для этого.

Еще нам нужен класс модель для БД, в нем у нас будет обозначен ID, Step ID и имя, самое важное для нас.

StepsDataModel.java
import io.realm.RealmObject;

public class StepsDataIModel extends RealmObject {

    private int id;
    private String stepID;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getStepID() {
        return stepID;
    }

    public void setStepID(String stepID) {
        this.stepID = stepID;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Этот класс мы унаследовали от RealmObject для того что бы этот класс использовался как таблица БД в Realm.

Ну и теперь все что нам осталось это создать класс который будет давать выбор что создавать, шаг или подшаг собственно главную кнопку или саб кнопку.

CreateStepActivity.java
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

import butterknife.ButterKnife;
import butterknife.OnClick;

public class CreateStepActivity extends Activity {

    public static final String RESULT = "result";
    public static final String RESULT_SECTION = "createSection";
    public static final String RESULT_UNDER_TOUR = "createUnderTour";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_check_step);
        ButterKnife.bind(this);
    }

   @OnClick(R.id.sectionBtn)
   public void onSectionClick() {
       Intent returnIntent = new Intent();
       returnIntent.putExtra(RESULT, RESULT_SECTION);
       setResult(RESULT_OK, returnIntent);
       finish();
   }

    @OnClick(R.id.underTourBtn)
    public void onSubClick() {
        Intent returnIntent = new Intent();
        returnIntent.putExtra(RESULT, RESULT_UNDER_TOUR);
        setResult(RESULT_OK, returnIntent);
        finish();
    }
}

Проинициализировав леяут и ButterKnife в onCreate() мы сказали что будем юзать ButterKnife. А дальше в onSectionClick() мы выбираем что создает новую главную кнопку, а в методе onSubClick() мы создаем саб. кнопку.

Ну и теперь леяут.

activity_check_step.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center_vertical"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="@string/textview_create"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:gravity="center_horizontal"
            android:orientation="horizontal">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:gravity="center_horizontal"
                android:orientation="vertical">

                <ImageButton
                    android:id="@+id/sectionBtn"
                    android:layout_width="wrap_content"
                    android:layout_height="110dp"
                    android:background="@null"
                    android:onClick="onSectionCLick"
                    android:src="@drawable/section_btn" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="10dp"
                    android:gravity="center_horizontal"
                    android:text="@string/textview_section"
                    android:textAppearance="?android:attr/textAppearanceSmall" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginLeft="20dp"
                android:gravity="center_horizontal"
                android:orientation="vertical">

                <ImageButton
                    android:id="@+id/underTourBtn"
                    android:layout_width="wrap_content"
                    android:layout_height="110dp"
                    android:background="@null"
                    android:onClick="onSubCLick"
                    android:src="@drawable/under_tour_btn" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="10dp"
                    android:gravity="center_horizontal"
                    android:text="@string/textview_under_tour"
                    android:textAppearance="?android:attr/textAppearanceSmall" />
            </LinearLayout>
        </LinearLayout>

    </LinearLayout>

</LinearLayout>

Тут мы создали две кнопки, по нажатию на одну мы создаем главную кнопку, по нажатию на вторую создаем саб. Картинки к кнопкам можете найти ниже.

 

А вот так будет выглядеть наш string.xml.

string.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Steps</string>

    <string name="textview_section">Line Segment</string>
    <string name="textview_under_tour">Sub Segment</string>
    <string name="textview_create">Create</string>

</resources>


Пробуем, компилируем после этого и смотрим работает ли. Все должно скомпилироваться и вы должны увидеть то же самое что я привел выше на скриншотах. Ну если что-то не сложилось, то всегда есть исходники :) С ними должно сто процентов заработать все. Главное не забыть прописать активити в AndroidManifest. А то будет падать. Может быть когда-нибудь дойдут руки переписать на лучший лад, а пока вот так...

Исходники:
GitHub

Комментариев нет:

Отправить комментарий