Найти в Дзене
Юрий Бухонов

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

Как то делал я один проект, в очередной раз как вы поняли. И заказчику понадобилось сделать вьюху, которая бы выглядела как пошаговая, знаете есть такие вьюхи типа вы пошагово выполняете какие-то действия, там авторизацию или регистрацию, так вот тут он захотел что бы такое было на создании одной инфы в приложении, вверху у нас есть эта пошаговая вьюха, а ниже у нас список с внесенными в него данными, в нашем случае я решил что это будут просто текстовые айтемы без какой либо логической нагрузки, так как по сути я хотел показать как я сделал связь между этой пошаговой вьюхой и списком.
Для начала давайте настроим проект перед стартом. Я буду использовать две библиотеки для созданния данного проекта, первая это Realm — для создания БД и вторая ButterKnife — для удобного подключения вьюх в проекте. Вот так будет выглядеть мой build.gradle:
build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.buil

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

Для начала давайте настроим проект перед стартом. Я буду использовать две библиотеки для созданния данного проекта, первая это 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. А то будет падать.