目標是建立一個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)上方有一個顯示密碼規則的區域(例如數字、大寫字母、小寫字母、特殊字元),下方是一個固定的輸入框和三個按鈕(回退、清理、確定),切換標籤頁時,下方的輸入框和按鈕保持不變,只是上方的密碼規則區域內容變化。
這樣我們可以採用TabLayout
和 ViewPager
的組合來實現,每個標籤頁對應一個 Fragment
,下方的輸入框和按鈕放在一個單獨的佈局中,使其在切換標籤頁時保持不變。
<?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 類【DigitFragment
、UppercaseLetterFragment
、LowercaseLetterFragment
、SpecialCharacterFragment
】,使用新的佈局檔案,並在 Fragment 中查詢並設定輸入框和按鈕的點選事件。
在實現左右滑動的同時,如果需要支援上下滑動,可以採用每個tab頁的內容使用了ListView來實現垂直滾動,而整體的tab切換使用了TabBar和TabBarView來管理