Android中的Layout_weight终极研究

本文详细解析了Android中LinearLayout布局的Layout_weight属性的工作原理,通过不同示例展示了如何使用该属性来调整视图间的空间分配。

以前在做UI布局时,也经常用Layout_weight属性,有时会遇到莫名其妙的布局问题,但总没研究懂。一直想做深入分析,但总是没耐心。遇到问题就找替代方法解决,但终非长久之计。这次下决心给它弄透!

以前一直没弄懂Layout_weight是什么意思,自己写代码测试也出来了不同的情况,最近看了一篇帖子感觉分析的很好,转贴出来学习下


布局文件是:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Button1"
/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="Button2"
/>
</LinearLayout>
出现的布局是:button1占了2/3,button2占了1/3。
57e34f80d09736ec6d811907.jpg.png

但是如果将布局文件中的button的属性android:layout_width="fill_parent"改为android:layout_width="wrap_content"那么出现的结果为:button1占了1/3,button2占了2/3。
3.png
出现这样的结局是什么意思呢?下面是人家的详解:转载过来:

*******转载的解释*********
linearLayout中包含有weight的child时,linearLayout会measure两次:
设屏幕宽度为X
第一次:button1的measuredWidth为X,button2也为X (因为用了weight,所以linearLayout每次measure child时不考虑前一个已经占用的大小),total_width为2X
第二次:计算delta=x-total_width=-x,然后会将button1的宽度设为
x+delta*1/3=0.66x, button2的宽度为 x+delta*2/3=0.33x
      那我现在对这句话重新概括一下:“因为设置了button1的权重最小,所以它占用的布局优先级就越高”,也许在Android里面布局并没有优先级之说,我这里只是为了说明问题,自己定义的,所以朋友们不要拍砖。
      那首先分析一下當layout_width屬性設置為fill_parent的時候,即充滿父佈局,當然意思是這個控件要根據weight的設置盡可能 的大,因此,依上例而論,button1的weight設為1,button2的weight設置為2.即button的優先級最高,因此,要填充父佈局 就要button1先來填充,盡可能的大,那這個盡可能又是多少呢,這就要綜合layout裡其他控件的weight值了,然後做一下運 算,button1佔據2/3,button2佔據1/3.你也可以把button2設置為一個非常大的數,比如2000,此時在Graphical Layout模式下可以看到button1填充滿了整個寬度,而看不到button2的影子,事實上button2還是存在的,你把鼠標指向 button1的後面就可以看到一個長長的豎條,那個就是button2,已經非常非常小了。因此,在layout_width設置為fill_parent的時候,weight所代表的是你的控件要優先盡可能的大。

     接著是當layout_weight設置為wrap_content的時候,即適應內容的寬度,意思是這個控件要盡可能的小,只要能把內容顯示出來 就可以了,同樣的,如果把button1和button2的layout_weight設置為wrap_content後,button1的weight 為1,button2的weight為2.那麼button1要優先盡可能的小,而button2也要盡可能的小,只是優先級不一樣,因為設置了 weight,所以這兩個控件總的寬度要填滿父佈局的寬度,所以就又要計算每個控件所佔據的大小,此時,button1的優先級較高,共有兩份,一份 1/3,一份2/3,button1要盡可能的小,那button1當然要選1/3,因此,我們看到的效果反而是button2佔據的較大。這裡要說的是 如果把權值同樣做如下設置:button1為1,button2為2000,那button1是不是就要佔據1/2000的空間呢?這麼理解就錯了,剛才 說了,要盡可能的小,但這個小是有一個限度的,那就 是wrap_content,就是還要是內容完完整整的顯示出來,同樣的,盡可能的大也是有一個限度的,那就是父佈局的寬度。因此,在 layout_width設置為wrap_content的時候,weight所代表的是你的控件要優先盡可能的大。
所以,要對weight做了解,要深深的理解下面兩句話:
在layout_width設置為fill_parent的時候,layout_weight所代表的是你的控件要優先盡可能的大,但這個大是有限度的,即fill_parent.
在layout_width設置為wrap_content的時候,layout_weight所代表的是你的控件要優先盡可能的小,但這個小是有限度的,即wrap_content.

layout_height 同 layout_width.



最后贴几张图出来:
1. layout_width="fill_parent", button1的weight=1,button2的weight=2;

1.png


2.layout_width="fill_parent", button1的weight=1,button2的weight=2000;
2.png


3.layout_width="wrap_content", button1的weight=1,button2的weight=2;



4.layout_width="wrap_content", button1的weight=1,button2的weight=2000;
4.png
*******转载的解释*********转载地址:http://hi.baidu.com/ljlkings/blog/item/fa2a59803f839a82f603a6b2.html?timeStamp=1305190390481

 

 

  SDK中的解释

 

Indicates how much of the extra space in the LinearLayout will be allocated to the view associated with these LayoutParams. Specify 0 if the view should not be stretched. Otherwise the extra pixels will be pro-rated among all views whose weight is greater than 0.

 

  重点有两个,一个是layout_weight表示LinearLayout中额外空间的划分(可能扩展应用layout_weight前的大小也可能压缩),另一个是按比例.

 

  以下说的都以 android:orientation="horizontal" 为例

 

  看了一下源码,虽说不太懂,但了解了下大概意思,按照自己的理解总结一下,直接写一下简化的代码吧(下面的代码是LinearLayout源文件中一部分的精简,变量名称含义可能不准确,为叙述方便暂作此解释):

 

复制代码
//Either expand children with weight to take up available space or
// shrink them if they extend beyond our current boundsint delta = widthSize - mTotalLength;
if (delta != 0 && totalWeight > 0.0f) {
    float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
    for (int i = 0; i < count; ++i) {
        final View child = getVirtualChildAt(i);

        if (child == null || child.getVisibility() == View.GONE) {
            continue;
        }
        
        final LinearLayout.LayoutParams lp =
                (LinearLayout.LayoutParams) child.getLayoutParams();

        float childExtra = lp.weight;
        if (childExtra > 0) {
            int share = (int) (childExtra * delta / weightSum);
       weightSum -= childExtra;
        delta  -= share;
            int childWidth = child.getMeasuredWidth() + share;
            if (childWidth < 0) {
                childWidth = 0;
            }
        }
    }
}
复制代码

 

变量含义

 

widthSize:     LinearLayout的宽度

 

mTotalLength:  所有子View的宽度的和(还没用考虑layout_weight)

 

totalWeight:   所有子View的layout_weight的和

 

mWeihtSUm:    LinearLayout的android:weightSum属性

 

过程分析:

 

首先计算出额外空间(可以为负)如果额外空间不为0并且有子View的layout_weight不为0的话按layout_weight分配额外空间:

 

int delta = widthSize - mTotalLength;
if (delta != 0 && totalWeight > 0.0f) {
  ...
}

 

如果LinearLayout设置了weightSum则覆盖子View的layout_weight的和:

 

float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

 

然后遍历LinearLayout的子元素,如果不为null且Visibility不为GONE的话,取得它的LayoutParams,如果它的layout_weight大于0,根据weightSum与它的weight计算出分配给它的额外空间

 

复制代码
if (childExtra > 0) {
    int share = (int) (childExtra * delta / weightSum);
   weightSum -= childExtra;
   delta -= share;

    int childWidth = child.getMeasuredWidth() + share;
    if (childWidth < 0) {
        childWidth = 0;
    }
}
复制代码

 


网上有解释说layout_weight表示重要程度,表示划分额外空间的优先级,通过代码可以知道这种观点是错误 的.layout_weight表示划分的比例,至于当View的layout_width为fill_parent时layout_weight比例相 反的问题按我的理解可以作以下解释:

 

比如说如下XML:

 

复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="#00ff00"
    android:weightSum="0"
    android:orientation="horizontal" >

    <Button
        android:id="@+id/imageViewLoginState"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:text="1" >
    </Button>

    <Button
        android:id="@+id/imageViewLoginState1"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:text="2" >
    </Button>

    <Button
        android:id="@+id/imageViewLoginState2"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="2"
        android:text="3" >
    </Button>

</LinearLayout>
复制代码

 



 

按一般理解,3个Button的比例应该为1:1:2,但实际情况是这样的:

 

 

按我的理解,系统是这样设置按钮的大小的,变量名按前面代码的意义:

 

假设Container即LinearLayout的宽度为PARENT_WIDTH

 

三个按钮的宽度都是FILL_PARENT,所以在应用layout_width之前,三个按钮的宽度都为PARENT_WIDTH

 

所以额外空间 delta = PARENT_WIDTH - 3 * PARENT_WIDTH = -2 * PARENT

 

因为LinearLayout没有设置android:weightSum(默认为0,设置为0就当没设置吧),所以 mWeightSum = 1 + 1 +2 =4

 

所以:

 

  第一个按钮的宽度为PARENT_WIDTH + share = PARENT_WIDTH + (layout_weight * delta / mWeightSum) = PARENT_WIDTH + (1 * (-2 * PARENT_WIDTH) /4) = 1 /2 *PARENT_WIDTH

 

    weightSum -= childExtra;(=3)
    delta  -= share;(=-3/2 * PARENT_WIDTH)

 

  第二个按钮的宽度为PARENT_WIDTH + share = PARENT_WIDTH + (layout_weight * delta / mWeightSum) = PARENT_WIDTH + (1 * (-3 / 2 * PARENT_WIDTH) /3) = 1 /2 *PARENT_WIDTH

 

    weightSum -= childExtra;(=2)
    delta  -= share;(=-PARENT_WIDTH)

 

  第三个按钮的宽度为PARENT_WIDTH + share = PARENT_WIDTH + (layout_weight * delta / mWeightSum) = PARENT_WIDTH + (2 * (- PARENT_WIDTH) /2) = 0

 

所以最终的而已就是前两个按钮平分LinearLayout,第三个按钮消失了.

 

 

 

  大致过程是这样,但不全对,比如如果上例中LinearLayout的weightSum设置为2的话,前两个按钮的宽度为0,但当计算第三个 按钮的宽度是mWeightSum = 0,但layout_weight * delta / mWeightSum无法计算,不知道系统怎么处理的,在我的能力之外了,weightSum为2时的效果图:

 

 

  weightSum为3时的效果图:

 

 

 

 

  SDK中说明的是,layout_weight表示额外空间怎么划分,要注意额外2字,要有额外的空间才可以将按比例将其分配给设置了 layout_weight的子View,所以,如果LinearLayout设置为WRAP_CONTENT的话是没有额外的空间 的,layout_weight就没有用处,所只要layout_width不设置为WRAP_CONTENT就行,也可以设置为具体的值,如果值太小的 话,额外空间为负,可能压缩子控件,使其大小比XML文件中定义的小,例如:

 

复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="100dp"
    android:layout_height="wrap_content"
    android:background="#00ff00"
    android:orientation="horizontal" >

    <Button
        android:id="@+id/button1"
        android:layout_width="60dp"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:text="1" >
    </Button>

    <Button
        android:id="@+id/button2"
        android:layout_width="60dp"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:text="2" >
    </Button>

    <Button
        android:id="@+id/button3"
        android:layout_width="60dp"
        android:layout_height="fill_parent"
        android:layout_weight="2"
        android:text="3" >
    </Button>

</LinearLayout>
复制代码

 

额外空间 delta = 100- 3 * 60 = -80

 

mWeightSum = 1 + 1 +2 =4

 

所以:

 

  第一个按钮的宽度为60+ share = 60 + (layout_weight * delta / mWeightSum) = 60 + (1 * (-80) /4) = 40

 

    weightSum -= childExtra;(=3)
    delta  -= share;(=-60)

 

  第二个按钮的宽度为60 + share = 60 + (layout_weight * delta / mWeightSum) = 60 + (1 * (-60) /3) = 40

 

    weightSum -= childExtra;(=2)
    delta  -= share;(=-40)

 

  第三个按钮的宽度为60 + share = 60 + (layout_weight * delta / mWeightSum) = 60 + (2 * (-40) /2) = 20

 

效果图:

 

 

 

 

以下代码也说明了layout_weight表示额外空间的分配:

 

复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="200dp"
    android:layout_height="wrap_content"
    android:background="#00ff00"
    android:orientation="horizontal" >

    <Button
        android:id="@+id/button1"
        android:layout_width="60dp"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:text="1" >
    </Button>

    <Button
        android:id="@+id/button2"
        android:layout_width="40dp"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:text="2" >
    </Button>

   
</LinearLayout>
复制代码

额外空间为100,所以Button1的宽度为60+100/2=110,Button2的宽度为40+100/2=90

 

package com.heytap.rjkf; import android.speech.tts.UtteranceProgressListener; import android.os.Bundle; import android.speech.tts.TextToSpeech; import android.app.Activity; import android.webkit.WebView; import android.webkit.WebViewClient; import android.content.res.Configuration; import android.widget.Button; import android.widget.Toast; import android.webkit.ValueCallback; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.SharedPreferences; import android.graphics.Color; import java.util.Locale; import java.util.UUID; import android.os.Handler; import java.util.regex.Pattern; public class MainActivity extends Activity implements TextToSpeech.OnInitListener { private WebView mWebView; private TextToSpeech tts; private StringBuilder textBuilder = new StringBuilder(); private boolean isSpeaking = false; private boolean isPaused = false; private int currentSentenceIndex = 0; private int currentCharIndex = 0; private String[] sentences; private Handler handler = new Handler(); private static final String PREFS_NAME = "ReadProgress"; private static final String KEY_SENTENCE_INDEX = "sentenceIndex"; private static final String KEY_CHAR_INDEX = "charIndex"; private boolean shouldHighlight = false; // 优化后的正则表达式常量 private static final Pattern BIBLE_REF_PATTERN = Pattern.compile( "\\b(?:创|亚|玛|太|可|启|但|何)\\s*?[一二百]*\\d+([、,]\\s*\\d+)*([~下]*\\d*[上]*)?[节]?\\b|" + "\\s*?[一二十百]*\\d+([、,]\\s*\\d+)*([~下]*\\d*[上]*)?[节]?|" + "(引用文|参\\s*(?:创|但何)\\s*?[一二三九十百]*\\d+([、,]\\s*\\d+)*([~下]*\\d*[上]*)?)" ); private static final Pattern SENTENCE_DELIMITER = Pattern.compile("(?<=[ 。!??!])"); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 检查是否有保存的进度 checkSavedProgress(); initTextToSpeech(); setupWebView(); loadWebContent("file:///android_asset/index.html"); setupButtons(); } private void initTextToSpeech() { tts = new TextToSpeech(this, this); setUtteranceProgressListener(); } private void checkSavedProgress() { SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); int savedSentenceIndex = prefs.getInt(KEY_SENTENCE_INDEX, -1); int savedCharIndex = prefs.getInt(KEY_CHAR_INDEX, -1); if (savedSentenceIndex != -1 && savedCharIndex != -1) { new AlertDialog.Builder(this) .setTitle("继续朗读?") .setMessage("检测到上次未完成的朗读进度,是否继续?") .setPositiveButton("是", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { currentSentenceIndex = savedSentenceIndex; currentCharIndex = savedCharIndex; shouldHighlight = true; // 标记需要高亮 } }) .setNegativeButton("否", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { clearProgress(); } }) .setCancelable(false) .show(); } } // JavaScript接口类 private class JavaScriptInterface { @android.webkit.JavascriptInterface public void highlightSentence(int sentenceIndex, int charIndex) { runOnUiThread(() -> { try { // 调用JS高亮方法 String js = String.format("highlightSentence(%d, %d)", sentenceIndex, charIndex); mWebView.evaluateJavascript(js, null); } catch (Exception e) { Toast.makeText(MainActivity.this, "高亮错误: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } }); } } private void setupWebView() { mWebView = findViewById(R.id.mWebView); mWebView.getSettings().setJavaScriptEnabled(true); mWebView.getSettings().setDomStorageEnabled(true); mWebView.addJavascriptInterface(new JavaScriptInterface(), "AndroidInterface"); mWebView.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); // 注入高亮JS injectHighlightJS(); extractTextFromWebPage(); // 如果需要恢复高亮 if (shouldHighlight) { highlightCurrentPosition(); shouldHighlight = false; } } }); } // 注入高亮JS脚本 private void injectHighlightJS() { String highlightJS = "javascript:(function() {" + " window.highlightSentence = function(sentenceIndex, charIndex) {" + " const sentences = document.querySelectorAll('.sentence');" + " sentences.forEach(s => s.style.backgroundColor = '');" + " if (sentenceIndex >= 0 && sentenceIndex < sentences.length) {" + " const sentence = sentences[sentenceIndex];" + " sentence.style.backgroundColor = '#FFF59D';" + " sentence.scrollIntoView({behavior: 'smooth', block: 'center'});" + " " + " // 高亮单个字符(可选)" + " const text = sentence.textContent;" + " if (charIndex > 0 && charIndex < text.length) {" + " const prefix = text.substring(0, charIndex);" + " const currentChar = text.substring(charIndex, charIndex + 1);" + " const suffix = text.substring(charIndex + 1);" + " sentence.innerHTML = prefix + '<span style=\"background-color:#FFCC80\">' + currentChar + '</span>' + suffix;" + " }" + " }" + " };" + " console.log('Highlight JS injected');" + "})()"; mWebView.evaluateJavascript(highlightJS, null); } private void extractTextFromWebPage() { mWebView.evaluateJavascript("(function() {" + "var nodes = document.body.childNodes;" + "var index = 0;" + "var sentences = [];" + "for (var i = 0; i < nodes.length; i++) {" + " var node = nodes[i];" + " if (node.nodeType === Node.TEXT_NODE) {" + " var text = node.nodeValue.trim();" + " if (text) {" + " var span = document.createElement('span');" + " span.className = 'sentence';" + " span.dataset.index = index++;" + " span.textContent = text;" + " node.parentNode.replaceChild(span, node);" + " sentences.push(text);" + " }" + " } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() !== 'br') {" + " var innerText = node.innerText.trim();" + " if (innerText) {" + " var span = document.createElement('span');" + " span.className = 'sentence';" + " span.dataset.index = index++;" + " span.textContent = innerText;" + " node.innerHTML = '';" + " node.appendChild(span);" + " sentences.push(innerText);" + " }" + " }" + "}" + "return sentences.join('|');" + "})()", new ValueCallback<String>() { @Override public void onReceiveValue(String value) { runOnUiThread(() -> { try { if (value != null && !value.isEmpty() && !"null".equals(value)) { textBuilder.setLength(0); // 移除JSON字符串的引号 String rawText = value.replaceAll("^\"|\"$", ""); textBuilder.append(rawText); // 使用预编译的正则表达式提高效率 String filteredText = BIBLE_REF_PATTERN.matcher(textBuilder.toString()).replaceAll(""); // 改进句子分割逻辑 sentences = SENTENCE_DELIMITER.split(filteredText); currentSentenceIndex = 0; currentCharIndex = 0; // 如果需要恢复高亮 if (shouldHighlight) { highlightCurrentPosition(); shouldHighlight = false; } } else { Toast.makeText(MainActivity.this, "页面内容为空", Toast.LENGTH_SHORT).show(); } } catch (Exception e) { Toast.makeText(MainActivity.this, "文本处理错误: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } }); } }); } private void highlightCurrentPosition() { if (mWebView != null) { String js = String.format("highlightSentence(%d, %d)", currentSentenceIndex, currentCharIndex); mWebView.evaluateJavascript(js, null); } } private void saveProgress() { SharedPreferences.Editor editor = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit(); editor.putInt(KEY_SENTENCE_INDEX, currentSentenceIndex); editor.putInt(KEY_CHAR_INDEX, currentCharIndex); editor.apply(); } private void clearProgress() { SharedPreferences.Editor editor = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit(); editor.remove(KEY_SENTENCE_INDEX); editor.remove(KEY_CHAR_INDEX); editor.apply(); } // 其他方法保持不变(setupButtons, stopSpeaking, setUtteranceProgressListener等) // 确保在onDestroy中保存当前进度 private void setupButtons() { Button speakButton = findViewById(R.id.speakButton); speakButton.setOnClickListener(v -> { if (sentences == null || sentences.length == 0) { Toast.makeText(MainActivity.this, "请先加载内容", Toast.LENGTH_SHORT).show(); return; } if (isPaused) { isPaused = false; speakFromCurrentPosition(); Toast.makeText(MainActivity.this, "朗读继续", Toast.LENGTH_SHORT).show(); } else { if (isSpeaking) { Toast.makeText(MainActivity.this, "正在朗读中", Toast.LENGTH_SHORT).show(); } else { currentSentenceIndex = 0; currentCharIndex = 0; speakFromCurrentPosition(); Toast.makeText(MainActivity.this, "朗读开始", Toast.LENGTH_SHORT).show(); } } }); Button stopButton = findViewById(R.id.stopButton); stopButton.setOnClickListener(v -> { stopSpeaking(); Toast.makeText(MainActivity.this, "朗读已停止", Toast.LENGTH_SHORT).show(); }); Button pauseButton = findViewById(R.id.pauseButton); pauseButton.setOnClickListener(v -> { if (isSpeaking) { tts.stop(); isSpeaking = false; isPaused = true; Toast.makeText(MainActivity.this, "朗读已暂停", Toast.LENGTH_SHORT).show(); } }); } private void stopSpeaking() { if (tts != null) { tts.stop(); } isSpeaking = false; isPaused = false; } private void setUtteranceProgressListener() { tts.setOnUtteranceProgressListener(new UtteranceProgressListener() { @Override public void onStart(String utteranceId) { isSpeaking = true; isPaused = false; } @Override public void onDone(String utteranceId) { handler.post(() -> { // 更新朗读位置(整句完成) if (currentSentenceIndex < sentences.length) { currentCharIndex = sentences[currentSentenceIndex].length(); } handler.postDelayed(MainActivity.this::speakFromCurrentPosition, 100); }); } @Override public void onError(String utteranceId) { isSpeaking = false; handler.post(() -> Toast.makeText(MainActivity.this, "朗读错误", Toast.LENGTH_SHORT).show() ); } }); } private void speakFromCurrentPosition() { if (tts == null || isPaused || sentences == null) return; try { // 跳过空句子 while (currentSentenceIndex < sentences.length && (sentences[currentSentenceIndex] == null || sentences[currentSentenceIndex].trim().isEmpty())) { currentSentenceIndex++; currentCharIndex = 0; } if (currentSentenceIndex >= sentences.length) { isSpeaking = false; Toast.makeText(this, "播放完成", Toast.LENGTH_SHORT).show(); return; } String currentSentence = sentences[currentSentenceIndex]; if (currentCharIndex >= currentSentence.length()) { // 移动到下一句 currentSentenceIndex++; currentCharIndex = 0; speakFromCurrentPosition(); return; } // 朗读当前句子剩余部分 String partToSpeak = currentSentence.substring(currentCharIndex); if (!partToSpeak.trim().isEmpty()) { Bundle params = new Bundle(); String utteranceId = UUID.randomUUID().toString(); params.putCharSequence(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId); tts.speak(partToSpeak, TextToSpeech.QUEUE_FLUSH, params, utteranceId); // 更新字符位置(整句朗读) currentCharIndex = currentSentence.length(); } else { // 跳过空白句子 currentSentenceIndex++; currentCharIndex = 0; speakFromCurrentPosition(); } } catch (Exception e) { Toast.makeText(this, "朗读异常: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } private void loadWebContent(String url) { try { mWebView.loadUrl(url); } catch (Exception e) { Toast.makeText(this, "内容加载失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); } @Override public void onBackPressed() { if (mWebView.canGoBack()) { mWebView.goBack(); } else { super.onBackPressed(); } } @Override public void onInit(int status) { if (status == TextToSpeech.SUCCESS) { int result = tts.setLanguage(Locale.CHINESE); if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Toast.makeText(this, "语言不支持", Toast.LENGTH_SHORT).show(); } else { // 设置朗读参数 tts.setPitch(1.0f); tts.setSpeechRate(1.0f); } } else { Toast.makeText(this, "TTS初始化失败", Toast.LENGTH_SHORT).show(); } } @Override protected void onDestroy() { // 保存当前进度 if (isSpeaking || isPaused) { saveProgress(); } else { clearProgress(); } if (tts != null) { tts.stop(); tts.shutdown(); } super.onDestroy(); } } 你看下我代码,是什么原有导致到第一次打开快速闪下能看到webbiew ,然后就突然黑了看不到,只能看到底部的三个按钮组件, 下面是布局:<?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:orientation="vertical"> <!-- WebView应占据主要空间 --> <WebView android:id="@+id/mWebView" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <!-- 按钮放在底部 --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center"> <Button android:id="@+id/speakButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="朗读"/> <Button android:id="@+id/pauseButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="暂停"/> <Button android:id="@+id/stopButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="停止"/> </LinearLayout> </LinearLayout>
最新发布
10-10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值