使用ViewPager和TabLayout來實現滑動切換效果

灿烂热烈發表於2024-07-24

目標是建立一個Android密碼輸入頁面,包含數字、大寫字母、小寫字母和特殊符號四個部分,同時支援上下滑動和左右滑動切換不同部分。

1.首先,在佈局檔案(activity_main.xml)中新增ViewPager和TabLayout:

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

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:tabIndicatorColor="@android:color/white"
        app:tabMode="scrollable" />
        
</RelativeLayout>

2.在Activity【或Fragment】中,設定ViewPager和TabLayout,並建立PagerAdapter用於管理不同部分的頁面:

package com.example.password;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import android.os.Bundle;
import com.google.android.material.tabs.TabLayout;

public class MainActivity extends AppCompatActivity {

    private ViewPager viewPager;
    private TabLayout tabLayout;

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

        viewPager = findViewById(R.id.viewPager);
        tabLayout = findViewById(R.id.tabLayout);

        // Create an adapter that returns a fragment for each of the four sections
        viewPager.setAdapter(new SectionsPagerAdapter(getSupportFragmentManager()));

        // Connect the ViewPager with TabLayout
        tabLayout.setupWithViewPager(viewPager);

        // Set tab titles
        tabLayout.getTabAt(0).setText("數字");
        tabLayout.getTabAt(1).setText("大寫字母");
        tabLayout.getTabAt(2).setText("小寫字母");
        tabLayout.getTabAt(3).setText("特殊符號");
    }

    // FragmentPagerAdapter for managing fragments for each section
    private class SectionsPagerAdapter extends FragmentPagerAdapter {

        public SectionsPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            switch (position) {
                case 0:
                    return new DigitFragment();
                case 1:
                    return new UppercaseLetterFragment();
                case 2:
                    return new LowercaseLetterFragment();
                case 3:
                    return new SpecialCharacterFragment();
                default:
                    return null;
            }
        }

        @Override
        public int getCount() {
            return 4; // Total number of sections
        }
    }
}

3.建立每個部分的Fragment:DigitFragment、UppercaseLetterFragment、LowercaseLetterFragment和SpecialCharacterFragment。這些Fragment分別顯示數字、大寫字母、小寫字母和特殊字元。每個Fragment都會簡單地顯示一個RecyclerView,用於顯示對應的密碼字元或者按鈕。

package com.example.password;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;

public class DigitFragment extends Fragment {

    private RecyclerView recyclerView;
    private PasswordAdapter adapter;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_password, container, false);

        recyclerView = rootView.findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); // Grid layout with 3 columns
        adapter = new PasswordAdapter(generateDigitList());
        recyclerView.setAdapter(adapter);

        return rootView;
    }

    private List<String> generateDigitList() {
        List<String> digits = new ArrayList<>();
        for (int i = 0; i <= 9; i++) {
            digits.add(String.valueOf(i));
        }
        return digits;
    }
}
package com.example.password;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;

public class UppercaseLetterFragment extends Fragment {

    private RecyclerView recyclerView;
    private PasswordAdapter adapter;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_password, container, false);

        recyclerView = rootView.findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); // Grid layout with 3 columns
        adapter = new PasswordAdapter(generateUppercaseList());
        recyclerView.setAdapter(adapter);

        return rootView;
    }

    private List<String> generateUppercaseList() {
        List<String> uppercaseLetters = new ArrayList<>();
        for (char c = 'A'; c <= 'Z'; c++) {
            uppercaseLetters.add(String.valueOf(c));
        }
        return uppercaseLetters;
    }
}
package com.example.password;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;

public class LowercaseLetterFragment extends Fragment {

    private RecyclerView recyclerView;
    private PasswordAdapter adapter;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_password, container, false);

        recyclerView = rootView.findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); // Grid layout with 3 columns
        adapter = new PasswordAdapter(generateLowercaseList());
        recyclerView.setAdapter(adapter);

        return rootView;
    }

    private List<String> generateLowercaseList() {
        List<String> lowercaseLetters = new ArrayList<>();
        for (char c = 'a'; c <= 'z'; c++) {
            lowercaseLetters.add(String.valueOf(c));
        }
        return lowercaseLetters;
    }
}
package com.example.password;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;

public class SpecialCharacterFragment extends Fragment {

    private RecyclerView recyclerView;
    private PasswordAdapter adapter;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_password, container, false);

        recyclerView = rootView.findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3)); // Grid layout with 3 columns
        adapter = new PasswordAdapter(generateSpecialCharacterList());
        recyclerView.setAdapter(adapter);

        return rootView;
    }

    private List<String> generateSpecialCharacterList() {
        List<String> specialCharacters = new ArrayList<>();
        // Add commonly used special characters here, adjust as per your requirements
        specialCharacters.add("!");
        specialCharacters.add("@");
        specialCharacters.add("#");
        specialCharacters.add("$");
        specialCharacters.add("%");
        specialCharacters.add("&");
        specialCharacters.add("*");
        specialCharacters.add("-");
        specialCharacters.add("+");
        specialCharacters.add("=");

        return specialCharacters;
    }
}

4.共用的 RecyclerView 介面卡 PasswordAdapter.java

package com.example.password;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;

public class PasswordAdapter extends RecyclerView.Adapter<PasswordAdapter.PasswordViewHolder> {

    private List<String> dataList;

    public PasswordAdapter(List<String> dataList) {
        this.dataList = dataList;
    }

    @NonNull
    @Override
    public PasswordViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_password, parent, false);
        return new PasswordViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull PasswordViewHolder holder, int position) {
        String item = dataList.get(position);
        holder.textView.setText(item);
    }

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

    static class PasswordViewHolder extends RecyclerView.ViewHolder {
        TextView textView;

        PasswordViewHolder(View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.text_view_item);
        }
    }
}

5.每個Fragment中,根據需要建立佈局和邏輯來顯示對應的密碼字元。

佈局檔案 fragment_password.xml

該佈局檔案用於每個Fragment中RecyclerView的佈局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="8dp"
    android:clipToPadding="false"
    android:scrollbars="vertical" />

佈局檔案 item_password.xml

該佈局檔案用於RecyclerView中每個項的佈局:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/text_view_item"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="16dp"
    android:textSize="18sp"
    android:background="?attr/selectableItemBackground"
    android:textColor="@android:color/black"
    android:gravity="center"
    android:textAppearance="?android:attr/textAppearanceListItem" />

現在展示出的頁面如下

那如果我們需要再給tab行下面加一個輸入框【要在每個標籤下方新增一個輸入框】 ,並新增回退、清理和確定按鈕。

也就是說:每個標籤頁(Tab)上方有一個顯示密碼規則的區域(例如數字、大寫字母、小寫字母、特殊字元),下方是一個固定的輸入框和三個按鈕(回退、清理、確定),切換標籤頁時,下方的輸入框和按鈕保持不變,只是上方的密碼規則區域內容變化。

這樣我們可以採用TabLayoutViewPager 的組合來實現,每個標籤頁對應一個 Fragment,下方的輸入框和按鈕放在一個單獨的佈局中,使其在切換標籤頁時保持不變。

1.更改佈局檔案 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:elevation="6dp"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/ThemeOverlay.MaterialComponents.ActionBar" />

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/tab_layout" />

    <LinearLayout
        android:id="@+id/password_controls_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_alignParentBottom="true"
        android:padding="16dp">

        <EditText
            android:id="@+id/edit_text_password"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:hint="Enter password"
            android:inputType="textPassword" />

        <Button
            android:id="@+id/button_back"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Back" />

        <Button
            android:id="@+id/button_clear"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Clear" />

        <Button
            android:id="@+id/button_confirm"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Confirm" />

    </LinearLayout>

</RelativeLayout>

2.修改MainActivity.java

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.google.android.material.tabs.TabLayout;

public class MainActivity extends AppCompatActivity {

    private EditText editTextPassword;
    private Button buttonBack, buttonClear, buttonConfirm;

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

        editTextPassword = findViewById(R.id.edit_text_password);
        buttonBack = findViewById(R.id.button_back);
        buttonClear = findViewById(R.id.button_clear);
        buttonConfirm = findViewById(R.id.button_confirm);

        ViewPager viewPager = findViewById(R.id.view_pager);
        TabLayout tabLayout = findViewById(R.id.tab_layout);

        // Set up ViewPager with adapter
        PagerAdapter pagerAdapter = new PagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(pagerAdapter);
        tabLayout.setupWithViewPager(viewPager);

        buttonBack.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!editTextPassword.getText().toString().isEmpty()) {
                    String currentText = editTextPassword.getText().toString();
                    editTextPassword.setText(currentText.substring(0, currentText.length() - 1));
                }
            }
        });

        buttonClear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                editTextPassword.setText("");
            }
        });

        buttonConfirm.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String password = editTextPassword.getText().toString();
                // Example: validatePassword(password);
                Toast.makeText(MainActivity.this, "Password entered: " + password, Toast.LENGTH_SHORT).show();
            }
        });
    }

    private class PagerAdapter extends FragmentPagerAdapter {

        private final String[] tabTitles = new String[]{"Digits", "Uppercase", "Lowercase", "Special"};

        public PagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            switch (position) {
                case 0:
                    return new DigitFragment();
                case 1:
                    return new UppercaseLetterFragment();
                case 2:
                    return new LowercaseLetterFragment();
                case 3:
                    return new SpecialCharacterFragment();
                default:
                    return null;
            }
        }

        @Override
        public int getCount() {
            return tabTitles.length;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return tabTitles[position];
        }
    }
}

3.更新 Fragment 類

更新每個標籤頁的 Fragment 類【DigitFragmentUppercaseLetterFragmentLowercaseLetterFragmentSpecialCharacterFragment】,使用新的佈局檔案,並在 Fragment 中查詢並設定輸入框和按鈕的點選事件。

在實現左右滑動的同時,如果需要支援上下滑動,可以採用每個tab頁的內容使用了ListView來實現垂直滾動,而整體的tab切換使用了TabBar和TabBarView來管理

相關文章