今天要來寫RecyclerView系列的第8彈!(沃草,第八篇關於RecyclerView?)

帶指標的橫向RecyclerView實作!

其他的

碼農日常-『Android studio』基本RecyclerView用法

碼農日常-『Android studio』基本RecyclerView 用法-2 基本版下拉更新以及點擊事件

碼農日常-『Android studio』基本RecyclerView 用法-3 RecyclerView上下滑動排序與側滑刪除(RecyclerView Swipe)

碼農日常-『Android studio』基本RecyclerView 用法-4 左滑顯示Button Menu

碼農日常-『Android studio』進階RecyclerView 用法-5 RecyclerView item混合介面

碼農日常-『Android studio』嵌套式RecyclerView(Nested RecyclerView)實現

碼農日常-『Android studio』RecyclerView自動捲動與控制捲動速度

 

完了,這系列真的會寫到不知道哪裡去...

 

好啦回歸正題,今天要寫的東西跟ViewPager有點接近,不過就我個人來說我覺得使用起來應該會比ViewPager稍微更簡單一些

用寫的有點抽象,我們來看一下Gif吧!

 

 

簡單來說吧,今天的實作主要就是下面的那一排「點點」

因為在UI的設計上來說,感覺橫向的物件沒有一個標記物還挺奇怪的...(不知道為什麼...

而有的時候再做開發時,感覺這種橫向的物件就使用ViewPager感覺又蠻殺雞焉用牛刀的

於是後來想著想著,不然就來做一個物件綁定RecyclerView的position就好啦...是這麼說啦

不過實做起來意外地燒腦了下

總之,既然看到了我寫的文章,那就便是希望能夠解決你的問題囉~

總之先上Github吧!

->點我

 


1. 撰寫「標記」的介面

整體來說,這次的實作有兩大步驟

次序上大致就是先完成「標記」的介面,再來完成關於RecyclerView的整體介面

因此,在文章的首先,我要先來完成標記的介面

 

首先,看一下這次的資料夾結構

截圖 2023-02-23 下午2.51.11

在第一段落中,首要目標是完成紅色&綠色部分

總之先從紅色的部分下手吧!

首先,先來畫一下dot_root.xml這隻檔案

dot_root.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

</FrameLayout>

這裡的畫面基本上就是

完全一片空白(笑)

不是我少PO,是因為這不份的重點是為之後要畫的介面製造一個載體,讓他可以被主界面給使用

好啦,再來就是重點了,如下

DotIndicator.java

public class DotIndicator extends View {
    private int thumbColor = Color.parseColor("#00A0AF");
    private int trackColor = Color.parseColor("#ADD0EB");

    private Paint paint;
    private int pageNum;
    private int currentPosition;
    private int gapSize;
    private float radius;
    private int colorOn;
    private int colorOff;


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

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

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

    public void setThumbColor(int thumbColor) {
        this.thumbColor = thumbColor;
    }

    public void setTrackColor(int trackColor) {
        this.trackColor = trackColor;
    }

    private void init() {
        radius = dp2px(10f);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        colorOn = thumbColor;
        colorOff = trackColor;
        gapSize = dp2px(30f);
    }
    //獲取現在的位置
    public void onPageScrolled(int position) {
        currentPosition = position;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (pageNum <= 0) {
            return;
        }
        //計算標籤的位置
        float left = (getWidth() - (pageNum - 1) * gapSize) * 0.5f;
        float height = getHeight() * 0.5f;
        paint.setColor(colorOff);
        //根據數量來畫出點點的數量
        for (int i = 0; i < pageNum; i++) {
            canvas.drawCircle(left + i * gapSize, height, radius, paint);
        }
        paint.setColor(colorOn);
        //設置被選中的點點將之設為深色
        canvas.drawCircle(left + currentPosition * gapSize,
                height, radius, paint);
    }

    //取得item的數量
    public void setPageNum(int num) {
        pageNum = num;
    }

    private int dp2px(float dp) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dp * scale * 0.5f);
    }
}

這裡說複雜也不複雜,其實有自己畫過介面的人應該多多少少都知道

這裡就是一個自己畫介面的組合

而我們在這裡的製作邏輯上,基本上就是用setPageNum(int num)取得RecyclerView所乘載的item的數量

再來在onDraw的綠色部分去依照item的數量去繪製相同數量的點點

再來使用onPageScrolled(int position)取得即時頁次的接口,而該頁次繪製成深色的部分就是在onDraw內使用橘色部分的程式碼去繪製

...大概就是這樣的一個流程

 

好的,寫到這裡就算是寫好「點點」的介面了

完成這部分的繪圖後,接下來就是要來撰寫控制器的部分了!

 


2. 撰寫控制器

 

接下來要撰寫的是讓ReyclerView的狀態綁定剛才的繪圖介面的部分

在RecyclerView的控制裡面,有個方法叫做addOnScrollListener,基本上可以理解為這個方法就是在RecyclerView被滑動時的反饋

而我們基本上就是運用這個功能下去完成實作的,如下

 

RecyclerViewIndicator.java

public class RecyclerViewIndicator extends FrameLayout {

    private final FrameLayout rootView;
    private DotIndicator dotIndicator;

    public RecyclerViewIndicator(@NonNull Context context) {
        this(context,null);
    }

    public RecyclerViewIndicator(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public RecyclerViewIndicator(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        View root = inflate(context, R.layout.dot_root, this);
        rootView = root.findViewById(R.id.root);
    }

    public DotIndicator getDotIndicator() {
        return dotIndicator;
    }

    public void setWithRecyclerView(RecyclerView view) {
        dotIndicator = new DotIndicator(view.getContext());

        rootView.post(() -> {
            int count = view.getAdapter().getItemCount();
            dotIndicator.setPageNum(count);
            rootView.addView(dotIndicator);
        });

        view.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                //獲取現在的位置,並更新給「點」
                LinearLayoutManager manager = ((LinearLayoutManager) recyclerView.getLayoutManager());
                assert manager != null;
                int current = manager.findLastVisibleItemPosition();
                dotIndicator.onPageScrolled(current);
            }
        });
    }
}

基本上重點都在setWithRecyclerView(RecyclerView view)裡面

而由於我們要讓畫介面的處在非同步下去畫,因此將介面的初始化部分寫在了綠色的裡面

最後,調用addOnScrollListener去對標籤做更新,初始化以及翻頁的事件就完成囉!

 

另外,在這支檔案最上面的三個建構子的部分也要注意好,我當時在寫的時候就因為沒注意好這裡一直閃退,查了很久才查出來...

 


3. 畫主介面UI

 

前面終於把基底寫好了(累

接下來就是來把其他部分給完成起來囉!

截圖 2023-02-23 下午2.51.11

首先是RecyclerView的item

iteml.xml

截圖 2023-02-23 下午5.06.01

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"
        app:cardCornerRadius="6dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="10dp">

            <TextView
                android:id="@+id/textView_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="TextView"
                android:textAppearance="@style/TextAppearance.AppCompat.Display1"
                android:textColor="#000000"
                android:textStyle="bold"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/textView_depiction"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="24dp"
                android:text="TextView"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/textView_title" />

            <ImageView
                android:id="@+id/imageView_tank"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/textView_depiction"
                app:srcCompat="@drawable/cromwell" />


        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>


</androidx.constraintlayout.widget.ConstraintLayout>

 

activity_main.xml

截圖 2023-02-23 下午5.37.10

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView_display"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/indicator"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.noahliu.horizontalrecyclerviewwithindicatorexample.RecyclerViewIndicator
        android:id="@+id/indicator"
        android:layout_width="match_parent"
        android:layout_height="10dp"
        app:layout_constraintBottom_toTopOf="@+id/guideline"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.8" />
</androidx.constraintlayout.widget.ConstraintLayout>

 


4. 撰寫主要功能

 

最後就是主要功能的部分了,這部分的話因為基本上都是RecyclerView的設置而已,我也不再多說廢話

首先是關於坦克的實體類

TanksData.java

package com.noahliu.horizontalrecyclerviewwithindicatorexample;

public class TanksData {

    private String title;
    private int image;
    private String depiction;

    public TanksData(String title, int image, String depiction) {
        this.title = title;
        this.image = image;
        this.depiction = depiction;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getImage() {
        return image;
    }

    public void setImage(int image) {
        this.image = image;
    }

    public String getDepiction() {
        return depiction;
    }

    public void setDepiction(String depiction) {
        this.depiction = depiction;
    }
}

 

MyAdapter.java

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    public ArrayList<TanksData> data;

    public MyAdapter(ArrayList<TanksData> data) {
        this.data = data;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        TextView tvTitle,tvDepiction;
        ImageView igTank;
        public ViewHolder(View itemView) {
            super(itemView);
            tvTitle = itemView.findViewById(R.id.textView_title);
            tvDepiction = itemView.findViewById(R.id.textView_depiction);
            igTank = itemView.findViewById(R.id.imageView_tank);

        }
    }

    //綁定標籤點點列
    public void setIndicator(RecyclerViewIndicator indicator,RecyclerView recyclerView){
        indicator.setWithRecyclerView(recyclerView);
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        TanksData tanksData = data.get(position);
        holder.tvTitle.setText(tanksData.getTitle());
        holder.tvDepiction.setText(tanksData.getDepiction());
        holder.igTank.setImageResource(tanksData.getImage());
    }

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

藍底白字的部分就是將標記綁定的地方優,千萬別忘了

最後,就是MainActivity的部分了

 

MainActivity.java

public class MainActivity extends AppCompatActivity {

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

        RecyclerViewIndicator indicator = findViewById(R.id.indicator);
        RecyclerView recyclerView = findViewById(R.id.recyclerView_display);
        recyclerView.setLayoutManager(new LinearLayoutManager(this
                , LinearLayoutManager.HORIZONTAL, false));
        //RecyclerViewItem在滑動時可自動置中的方法
        LinearSnapHelper snapHelper = new LinearSnapHelper();
        snapHelper.attachToRecyclerView(recyclerView);

        ArrayList<TanksData> data = new ArrayList<>();
        data.add(new TanksData("Mark I坦克",R.drawable.mark,"這是第一種被正式命名為坦克的裝甲戰鬥車輛,於1916年首次出現在戰場上。"));
        data.add(new TanksData("Churchill坦克",R.drawable.churchill,"在第二次世界大戰期間,Churchill坦克是英國陸軍的主要戰車之一,擁有強大的裝甲和火力,是一款可靠的坦克。"));
        data.add(new TanksData("Cromwell坦克",R.drawable.cromwell,"在第二次世界大戰期間,Cromwell坦克是英國陸軍的主要戰車之一,擁有高速度和卓越的機動性。"));
        data.add(new TanksData("Centurion坦克",R.drawable.centurion,"在冷戰期間,Centurion坦克是英國陸軍主力坦克之一,經過多次改進後,成為了一款強大的主戰坦克。"));
        data.add(new TanksData("Challenger 2坦克",R.drawable.challenger,"是當代英國陸軍主力坦克之一,具有強大的裝甲和火力,並且具有良好的防禦能力。"));
        data.add(new TanksData("Warrior步兵戰車",R.drawable.warrior,"是一款由英國製造的輪式裝甲車,是英國陸軍中的主要裝甲車之一。"));
        MyAdapter adapter = new MyAdapter(data);
        recyclerView.setAdapter(adapter);
        //設置「點」進入綁定RecyclerView
        adapter.setIndicator(indicator,recyclerView);

    }
}

 

兩個重點,首先是綠底的部分,這邊的意思是讓RecyclerView在滑動時會自動讓item跑到中間的方法

粉底白字的就是我們本篇的重點,也就是讓RecyclerView與標記綁定的部分囉

 


 

我從開始寫Android到現在,也真的是遇到過了不少像這樣要自行畫介面的需求

而每次畫介面都是考驗邏輯跟考驗數學的時候(嘆

不過這次的也算簡單的了啦,就是剛開始有點不知道從何下手

好啦,無論如何,也是希望這篇文章對你有幫助,如果覺得我寫得不錯的話...

TK

arrow
arrow
    創作者介紹
    創作者 碼農日常 的頭像
    碼農日常

    碼農日常大小事

    碼農日常 發表在 痞客邦 留言(0) 人氣()