Android Studio PageSplitter应用启动崩溃求助
Android 长文本分页应用启动崩溃排查
我是Android Studio新手,正在开发一款将长文本拆分为多页的简单应用。找到的旧PageSplitter项目示例在新版Android Studio中出现大量报错,调试后模拟器启动时应用刚打开就终止,恳请帮忙排查问题,相关代码如下:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="ru.appheads.pagesplitterapp"> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Pagesplitterapp" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
MainActivity.java
package ru.appheads.pagesplitterapp; import android.os.Bundle; import android.text.TextPaint; import android.view.ViewTreeObserver; import androidx.fragment.app.FragmentActivity; import androidx.viewpager.widget.ViewPager; public class MainActivity extends FragmentActivity { private ViewPager pagesView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); pagesView = (ViewPager) findViewById(R.id.pages); pagesView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { PageSplitter pageSplitter = new PageSplitter(pagesView.getWidth(), pagesView.getHeight(), 1, 0); TextPaint textPaint = new TextPaint(); textPaint.setTextSize(getResources().getDimension(R.dimen.text_size)); for (int i = 0; i < 1000; i++) { pageSplitter.append("Hello, ", textPaint); textPaint.setFakeBoldText(true); pageSplitter.append("world", textPaint); textPaint.setFakeBoldText(false); pageSplitter.append("! ", textPaint); if ((i + 1) % 100 == 0) { pageSplitter.append("\n", textPaint); } } pagesView.setAdapter(new TextPagerAdapter(getSupportFragmentManager(), pageSplitter.getPages())); pagesView.getViewTreeObserver().removeOnGlobalLayoutListener(this); } }); } }
PageFragment.java
package ru.appheads.pagesplitterapp; import android.os.Bundle; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.fragment.app.Fragment; public class PageFragment extends Fragment { private final static String PAGE_TEXT = "PAGE_TEXT"; public static PageFragment newInstance(CharSequence pageText) { PageFragment frag = new PageFragment(); Bundle args = new Bundle(); args.putCharSequence(PAGE_TEXT, pageText); frag.setArguments(args); return frag; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { CharSequence text = getArguments().getCharSequence(PAGE_TEXT); TextView pageView = (TextView) inflater.inflate(R.layout.page, container, false); pageView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.text_size)); pageView.setText(text); return pageView; } }
PageSplitter.java
package ru.appheads.pagesplitterapp; import android.graphics.Typeface; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.TextPaint; import android.text.style.StyleSpan; import java.util.ArrayList; import java.util.List; public class PageSplitter { private final int pageWidth; private final int pageHeight; private final float lineSpacingMultiplier; private final int lineSpacingExtra; private final List<CharSequence> pages = new ArrayList<CharSequence>(); private SpannableStringBuilder currentLine = new SpannableStringBuilder(); private SpannableStringBuilder currentPage = new SpannableStringBuilder(); private int currentLineHeight; private int pageContentHeight; private int currentLineWidth; private int textLineHeight; public PageSplitter(int pageWidth, int pageHeight, float lineSpacingMultiplier, int lineSpacingExtra) { this.pageWidth = pageWidth; this.pageHeight = pageHeight; this.lineSpacingMultiplier = lineSpacingMultiplier; this.lineSpacingExtra = lineSpacingExtra; } public void append(String text, TextPaint textPaint) { textLineHeight = (int) Math.ceil(textPaint.getFontMetrics(null) * lineSpacingMultiplier + lineSpacingExtra); String[] paragraphs = text.split("\n", -1); int i; for (i = 0; i < paragraphs.length - 1; i++) { appendText(paragraphs[i], textPaint); appendNewLine(); } appendText(paragraphs[i], textPaint); } private void appendText(String text, TextPaint textPaint) { String[] words = text.split(" ", -1); int i; for (i = 0; i < words.length - 1; i++) { appendWord(words[i] + " ", textPaint); } appendWord(words[i], textPaint); } private void appendNewLine() { currentLine.append("\n"); checkForPageEnd(); appendLineToPage(textLineHeight); } private void checkForPageEnd() { if (pageContentHeight + currentLineHeight > pageHeight) { pages.add(currentPage); currentPage = new SpannableStringBuilder(); pageContentHeight = 0; } } private void appendWord(String appendedText, TextPaint textPaint) { int textWidth = (int) Math.ceil(textPaint.measureText(appendedText)); if (currentLineWidth + textWidth >= pageWidth) { checkForPageEnd(); appendLineToPage(textLineHeight); } appendTextToLine(appendedText, textPaint, textWidth); } private void appendLineToPage(int textLineHeight) { currentPage.append(currentLine); pageContentHeight += currentLineHeight; currentLine = new SpannableStringBuilder(); currentLineHeight = textLineHeight; currentLineWidth = 0; } private void appendTextToLine(String appendedText, TextPaint textPaint, int textWidth) { currentLineHeight = Math.max(currentLineHeight, textLineHeight); currentLine.append(renderToSpannable(appendedText, textPaint)); currentLineWidth += textWidth; } public List<CharSequence> getPages() { List<CharSequence> copyPages = new ArrayList<CharSequence>(pages); SpannableStringBuilder lastPage = new SpannableStringBuilder(currentPage); if (pageContentHeight + currentLineHeight > pageHeight) { copyPages.add(lastPage); lastPage = new SpannableStringBuilder(); } lastPage.append(currentLine); copyPages.add(lastPage); return copyPages; } private SpannableString renderToSpannable(String text, TextPaint textPaint) { SpannableString spannable = new SpannableString(text); if (textPaint.isFakeBoldText()) { spannable.setSpan(new StyleSpan(Typeface.BOLD), 0, spannable.length(), 0); } return spannable; } }
TextPagerAdapter.java
package ru.appheads.pagesplitterapp; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; import java.util.List; public class TextPagerAdapter extends FragmentPagerAdapter { private final List<CharSequence> pageTexts; public TextPagerAdapter(FragmentManager fm, List<CharSequence> pageTexts) { super(fm); this.pageTexts = pageTexts; } @Override public Fragment getItem(int i) { return PageFragment.newInstance(pageTexts.get(i)); } @Override public int getCount() { return pageTexts.size(); } }
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context="ru.appheads.pagesplitterapp.MainActivity"> <androidx.viewpager2.widget.ViewPager2 xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/pages" android:overScrollMode="never" android:layout_height="match_parent" android:layout_width="match_parent"> </androidx.viewpager2.widget.ViewPager2> </RelativeLayout>
page.xml
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:lineSpacingMultiplier="1" android:lineSpacingExtra="0sp"> </TextView>
styles.xml
<resources> <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar"> </style> </resources>
build.gradle(app)
plugins { id 'com.android.application' } android { compileSdk 32 defaultConfig { applicationId "ru.appheads.pagesplitterapp" minSdk 21 targetSdk 32 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'androidx.appcompat:appcompat:1.5.0' implementation 'com.google.android.material:material:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.viewpager2:viewpager2:1.0.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' }
崩溃原因及修复方案
1. ViewPager与ViewPager2类型不匹配
布局文件用的是ViewPager2,但代码里声明的是旧版ViewPager,强转时直接崩溃。
- 修复:把
MainActivity里的ViewPager改为ViewPager2,移除强制类型转换:private ViewPager2 pagesView; // ... pagesView = findViewById(R.id.pages);
2. 适配器不兼容ViewPager2
TextPagerAdapter继承的FragmentPagerAdapter是旧版ViewPager的适配器,ViewPager2需要用FragmentStateAdapter。
- 修复:重写适配器:
同时修改package ru.appheads.pagesplitterapp; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.viewpager2.adapter.FragmentStateAdapter; import java.util.List; public class TextPagerAdapter extends FragmentStateAdapter { private final List<CharSequence> pageTexts; public TextPagerAdapter(FragmentActivity activity, List<CharSequence> pageTexts) { super(activity); this.pageTexts = pageTexts; } @Override public Fragment createFragment(int position) { return PageFragment.newInstance(pageTexts.get(position)); } @Override public int getItemCount() { return pageTexts.size(); } }MainActivity中设置适配器的代码:pagesView.setAdapter(new TextPagerAdapter(MainActivity.this, pageSplitter.getPages()));
3. PageSplitter字体度量计算错误
textPaint.getFontMetrics(null)会返回0,因为传入null无法获取有效字体参数,导致行高计算异常。
- 修复:创建FontMetrics对象传入:
Paint.FontMetrics metrics = new Paint.FontMetrics(); textPaint.getFontMetrics(metrics); textLineHeight = (int) Math.ceil((metrics.descent - metrics.ascent) * lineSpacingMultiplier + lineSpacingExtra);
4. 主题不兼容
旧版android:Theme.Holo.Light.DarkActionBar与AndroidX组件存在兼容性问题。
- 修复:替换为AndroidX兼容主题:
<resources> <style name="Theme.Pagesplitterapp" parent="Theme.MaterialComponents.Light.DarkActionBar"> <item name="colorPrimary">@color/purple_500</item> <item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorOnPrimary">@color/white</item> <item name="android:statusBarColor">?attr/colorPrimaryVariant</item> </style> </resources>
5. 缺失资源检查
确保res/values/dimens.xml中存在text_size定义:
<dimen name="text_size">16sp</dimen>
若未创建data_extraction_rules.xml和backup_rules.xml,可移除AndroidManifest.xml中对应的属性,避免资源找不到的错误。
内容的提问来源于stack exchange,提问作者user2430364
相关产品推荐
相关产品推荐

