Android textview 选取框滞后

Android textview marquee lag

我正在尝试在我的 Exo 媒体播放器中实现顶部的 TextView 标题,并带有选取框动画。 据我所知,我已经设置了所有必要的属性,它确实有效,但随着文本滚动,我得到了一个奇怪的 lag/the 动画 stop/restarts。我怀疑这与焦点有关? 你怎么看,你知道如何解决这个问题吗? 这是我的代码:

玩家activity:

public class MediaPlayerActivity extends Activity implements View.OnClickListener,
    PlaybackControlView.VisibilityListener,
    ExoPlayer.EventListener,
    AdEvent.AdEventListener,
    AdErrorEvent.AdErrorListener {

public static final String ACTION_VIEW = "cambium.com.il.sport5.action.VIEW";
public static final String EXTENSION_EXTRA = "extension";
public static final String ACTION_VIEW_LIST = "cambium.com.il.sport5.action.VIEW_LIST";
public static final String URI_LIST_EXTRA = "uri_list";
public static final String EXTENSION_LIST_EXTRA = "extension_list";
private static final String LOG_TAG = "MediaPlayer-ImaAds";

/**
 * Constants
 */
private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
private static final CookieManager DEFAULT_COOKIE_MANAGER;

static {
    DEFAULT_COOKIE_MANAGER = new CookieManager();
    DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
}

/**
 * General Obj
 */
private GeneralUtils mGeneralUtils;
private SimpleExoPlayerView simpleExoPlayerView;
private Handler mainHandler;
private DataSource.Factory mediaDataSourceFactory;
private SimpleExoPlayer player;
private DefaultTrackSelector trackSelector;
private TrackSelectionHelper trackSelectionHelper;
private Timeline.Window window;
private boolean isTimelineStatic;
private boolean playerNeedsSource;
private long playerPosition;
private int playerWindow;
private boolean shouldAutoPlay;

/**
 * UI
 */
private View rootView;
private PlaybackControlView controlsRootView;
private Button retryButton;
private RelativeLayout mediaPlayerLoader;
private LinearLayout mTopBar;
private CustomTextView mVideoTitle;
private ImageButton mVideoBackBtn;

/**
 * IMA ads
 */
/* Factory class for creating SDK objects */
private ImaSdkFactory mSdkFactory;
/* The AdsLoader instance exposes the requestAds method */
private AdsLoader mAdsLoader;
/* AdsManager exposes methods to control ad playback and listen to ad events */
private AdsManager mAdsManager;
/* Whether an ad is displayed */
private boolean mIsAdDisplayed;


/**
 * =================
 * Lifecycle methods
 * =================
 */

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    initActivity();
    setContentView(R.layout.activity_media_player);
    getViews();
    setListeners();
    createAdsLoader();
}

@Override
public void onNewIntent(Intent intent) {
    releasePlayer();
    isTimelineStatic = false;
    setIntent(intent);
}

@Override
public void onResume() {
    if (mAdsManager != null && mIsAdDisplayed) {
        mAdsManager.resume();
    } else if (player != null && player.getPlaybackState() == ExoPlayer.STATE_READY) {
        player.setPlayWhenReady(true);
    }
    super.onResume();
}

@Override
public void onPause() {
    if (mAdsManager != null && mIsAdDisplayed) {
        mAdsManager.pause();
    }
    if (player != null)
        player.setPlayWhenReady(false);
    super.onPause();
}


@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    } else {
        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    }
}

@Override
protected void onStop() {
    releasePlayer();
    window = null;
    mediaDataSourceFactory = null;
    mainHandler = null;
    mSdkFactory = null;
    mAdsLoader = null;
    mAdsLoader = null;
    isTimelineStatic = false;
    finish();
    super.onStop();
}

@Override
public void onBackPressed() {
    super.onBackPressed();
}


/**
 * =================
 * Helper methods
 * =================
 */

private void initActivity() {
    mGeneralUtils = GeneralUtils.getInstance();
    mediaDataSourceFactory = buildDataSourceFactory(true);
    mainHandler = new Handler();
    window = new Timeline.Window();
    if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
        CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER);
    }
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}

private void getViews() {
    rootView = findViewById(R.id.root);
    controlsRootView = (PlaybackControlView) findViewById(R.id.controls_root);
    retryButton = (Button) findViewById(R.id.retry_button);
    simpleExoPlayerView = (SimpleExoPlayerView) findViewById(R.id.player_view);
    mediaPlayerLoader = (RelativeLayout) findViewById(R.id.mediaPlayerLoader);
    mTopBar = (LinearLayout) findViewById(R.id.topBar);
    mVideoBackBtn = (ImageButton) findViewById(R.id.videoBackBtn);
    mVideoTitle = (CustomTextView) findViewById(R.id.videoPlayerTitle);
    mVideoTitle.setSelected(true);
    mVideoTitle.setFocusable(true);
    mVideoTitle.setFocusableInTouchMode(true);


}

private void setListeners() {
    rootView.setOnClickListener(this);
    simpleExoPlayerView = (SimpleExoPlayerView) findViewById(R.id.player_view);
    simpleExoPlayerView.setControllerVisibilityListener(this);
    simpleExoPlayerView.requestFocus();
    retryButton.setOnClickListener(this);
    mVideoBackBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            onBackPressed();
        }
    });
}

private void showToast(int messageId) {
    showToast(getString(messageId));
}

private void showToast(String message) {
    Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
}


/**
 * =================
 * Exo player
 * =================
 */

private void initPlayer() {
    Intent intent = getIntent();
    String action = intent.getAction();
    if (player == null) {
        TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveVideoTrackSelection.Factory(BANDWIDTH_METER);
        trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
        trackSelectionHelper = new TrackSelectionHelper(trackSelector, videoTrackSelectionFactory);
        player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultLoadControl());
        player.addListener(this);
        simpleExoPlayerView.setPlayer(player);

        if (isTimelineStatic) {
            if (playerPosition == C.TIME_UNSET) {
                player.seekToDefaultPosition(playerWindow);
            } else {
                player.seekTo(playerWindow, playerPosition);
            }
        }
        player.setPlayWhenReady(shouldAutoPlay);
        playerNeedsSource = true;

        if (playerNeedsSource) {
            Uri[] uris;
            String[] extensions;
            if (ACTION_VIEW.equals(action)) {
                uris = new Uri[]{intent.getData()};
                extensions = new String[]{intent.getStringExtra(EXTENSION_EXTRA)};
            } else if (ACTION_VIEW_LIST.equals(action)) {
                String[] uriStrings = intent.getStringArrayExtra(URI_LIST_EXTRA);
                uris = new Uri[uriStrings.length];
                for (int i = 0; i < uriStrings.length; i++) {
                    uris[i] = Uri.parse(uriStrings[i]);
                }
                extensions = intent.getStringArrayExtra(EXTENSION_LIST_EXTRA);
                if (extensions == null) {
                    extensions = new String[uriStrings.length];
                }
            } else {
                showToast(getString(R.string.unexpected_intent_action, action));
                return;
            }
            MediaSource[] mediaSources = new MediaSource[uris.length];
            for (int i = 0; i < uris.length; i++) {
                mediaSources[i] = buildMediaSource(uris[i], extensions[i]);
            }
            MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]
                    : new ConcatenatingMediaSource(mediaSources);
            player.prepare(mediaSource, !isTimelineStatic, !isTimelineStatic);
            playerNeedsSource = false;
            mGeneralUtils.zoomOutAnimation(mediaPlayerLoader);
            updateButtonVisibilities();
        }
    }
}

private void releasePlayer() {
    if (player != null) {
        playerWindow = player.getCurrentWindowIndex();
        playerPosition = C.TIME_UNSET;
        Timeline timeline = player.getCurrentTimeline();
        if (!timeline.isEmpty() && timeline.getWindow(playerWindow, window).isSeekable) {
            playerPosition = player.getCurrentPosition();
        }
        player.release();
        player = null;
        trackSelector = null;
        trackSelectionHelper = null;
    }
}

private void updateButtonVisibilities() {
    controlsRootView.removeAllViews();
    retryButton.setVisibility(playerNeedsSource ? View.VISIBLE : View.GONE);
    controlsRootView.addView(retryButton);

    if (player == null) {
        return;
    }
}

private void showControls() {

    controlsRootView.setVisibility(View.VISIBLE);

}

private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) {
    return ((Sport5App) getApplication())
            .buildDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
}

private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
    int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
            : uri.getLastPathSegment());
    switch (type) {
        case C.TYPE_SS:
            return new SsMediaSource(uri, buildDataSourceFactory(false),
                    new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, null);
        case C.TYPE_DASH:
            return new DashMediaSource(uri, buildDataSourceFactory(false),
                    new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, null);
        case C.TYPE_HLS:
            return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, null);
        case C.TYPE_OTHER:
            return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
                    mainHandler, null);
        default: {
            throw new IllegalStateException("Unsupported type: " + type);
        }
    }
}


/**
 * =================
 * Listener methods
 * =================
 */

@Override /* View.OnClickListener  */
public void onClick(View view) {
    if (view == retryButton) {
        shouldAutoPlay = true;
        initPlayer();
    } else if (view.getParent() == controlsRootView) {
        MappingTrackSelector.MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
        if (mappedTrackInfo != null) {
            trackSelectionHelper.showSelectionDialog(this, ((Button) view).getText(),
                    trackSelector.getCurrentMappedTrackInfo(), (int) view.getTag());
        }
    }
}

@Override /* PlaybackControlView.VisibilityListener */
public void onVisibilityChange(int visibility) {
    controlsRootView.setVisibility(visibility);
    mTopBar.setVisibility(visibility);
}

@Override /* ExoPlayer.EventListener */
public void onTimelineChanged(Timeline timeline, Object manifest) {
    isTimelineStatic = !timeline.isEmpty()
            && !timeline.getWindow(timeline.getWindowCount() - 1, window).isDynamic;
}

@Override /* ExoPlayer.EventListener */
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
    updateButtonVisibilities();
    MappingTrackSelector.MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
    if (mappedTrackInfo != null) {
        if (mappedTrackInfo.getTrackTypeRendererSupport(C.TRACK_TYPE_VIDEO)
                == MappingTrackSelector.MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
            showToast(R.string.error_unsupported_video);
        }
        if (mappedTrackInfo.getTrackTypeRendererSupport(C.TRACK_TYPE_AUDIO)
                == MappingTrackSelector.MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
            showToast(R.string.error_unsupported_audio);
        }
    }
}

@Override /* ExoPlayer.EventListener */
public void onLoadingChanged(boolean isLoading) {

}

@Override /* ExoPlayer.EventListener */
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
    if (playbackState == ExoPlayer.STATE_ENDED) {
        // Handle completed event for playing post-rolls.
        if (mAdsLoader != null) {
            mAdsLoader.contentComplete();
        }
        showControls();
        finish();

    }

    if (playbackState == ExoPlayer.STATE_BUFFERING) {
        // Handle completed event for playing post-rolls.
        /*Toast.makeText(this, "STATE_BUFFERING", Toast.LENGTH_SHORT).show();*/
    }

    if (playbackState == ExoPlayer.STATE_IDLE) {
        // Handle completed event for playing post-rolls.
        /*Toast.makeText(this, "STATE_IDLE", Toast.LENGTH_SHORT).show();*/
    }

    if (playbackState == ExoPlayer.STATE_READY) {
        // Handle completed event for playing post-rolls.
        /*Toast.makeText(this, "STATE_READY", Toast.LENGTH_SHORT).show();*/
    }


    updateButtonVisibilities();
}

@Override /* ExoPlayer.EventListener */
public void onPlayerError(ExoPlaybackException e) {
    String errorString = null;
    if (e.type == ExoPlaybackException.TYPE_RENDERER) {
        Exception cause = e.getRendererException();
        if (cause instanceof MediaCodecRenderer.DecoderInitializationException) {
            // Special case for decoder initialization failures.
            MediaCodecRenderer.DecoderInitializationException decoderInitializationException =
                    (MediaCodecRenderer.DecoderInitializationException) cause;
            if (decoderInitializationException.decoderName == null) {
                if (decoderInitializationException.getCause() instanceof MediaCodecUtil.DecoderQueryException) {
                    errorString = getString(R.string.error_querying_decoders);
                } else if (decoderInitializationException.secureDecoderRequired) {
                    errorString = getString(R.string.error_no_secure_decoder,
                            decoderInitializationException.mimeType);
                } else {
                    errorString = getString(R.string.error_no_decoder,
                            decoderInitializationException.mimeType);
                }
            } else {
                errorString = getString(R.string.error_instantiating_decoder,
                        decoderInitializationException.decoderName);
            }
        }
    }
    if (errorString != null) {
        showToast(errorString);
    }
    playerNeedsSource = true;
    updateButtonVisibilities();
    showControls();
}

@Override /* ExoPlayer.EventListener */
public void onPositionDiscontinuity() {
}

@Override /* AdErrorEvent.AdErrorListener */
public void onAdError(AdErrorEvent adErrorEvent) {
    Log.e(LOG_TAG, "Ad Error: " + adErrorEvent.getError().getMessage());
    if (player != null)
        player.setPlayWhenReady(true);
    else {
        shouldAutoPlay = true;
        initPlayer();
    }

}

@Override /* AdEvent.AdEventListener */
public void onAdEvent(AdEvent adEvent) {
    Log.i(LOG_TAG, "Event: " + adEvent.getType());

    // These are the suggested event types to handle. For full list of all ad event
    // types, see the documentation for AdEvent.AdEventType.
    switch (adEvent.getType()) {
        case LOADED:
            // AdEventType.LOADED will be fired when ads are ready to be played.
            // AdsManager.start() begins ad playback. This method is ignored for VMAP or
            // ad rules playlists, as the SDK will automatically start executing the
            // playlist.
            mGeneralUtils.zoomOutAnimation(mediaPlayerLoader);
            mAdsManager.start();
            break;
        case CONTENT_PAUSE_REQUESTED:
            // AdEventType.CONTENT_PAUSE_REQUESTED is fired immediately before a video
            // ad is played.
            mIsAdDisplayed = true;
            player.setPlayWhenReady(false);
            break;
        case CONTENT_RESUME_REQUESTED:
            // AdEventType.CONTENT_RESUME_REQUESTED is fired when the ad is completed
            // and you should start playing your content.
            mIsAdDisplayed = false;
            if (player != null)
                player.setPlayWhenReady(true);
            else {
                shouldAutoPlay = true;
                initPlayer();
            }

            break;
        case ALL_ADS_COMPLETED:
            if (mAdsManager != null) {
                mAdsManager.destroy();
                mAdsManager = null;

            }
            break;
        default:
            break;
    }
}


/**
 * ================
 * IMA ADS
 * ================
 */

private void createAdsLoader() {
    String adUrl = DataUtils.getInstance().getAdsAdmin().getAdInPlayerUrl();
    adUrl = null;
    // Create an AdsLoader.
    mSdkFactory = ImaSdkFactory.getInstance();
    mAdsLoader = mSdkFactory.createAdsLoader(this);
    // Add listeners for when ads are loaded and for errors.
    mAdsLoader.addAdErrorListener(this);
    mAdsLoader.addAdsLoadedListener(new AdsLoader.AdsLoadedListener() {
        @Override
        public void onAdsManagerLoaded(AdsManagerLoadedEvent adsManagerLoadedEvent) {
            // Ads were successfully loaded, so get the AdsManager instance. AdsManager has
            // events for ad playback and errors.
            mAdsManager = adsManagerLoadedEvent.getAdsManager();
            // Attach event and error event listeners.
            mAdsManager.addAdErrorListener(MediaPlayerActivity.this);
            mAdsManager.addAdEventListener(MediaPlayerActivity.this);
            mAdsManager.init();
            shouldAutoPlay = false;
            initPlayer();
        }
    });

    if (adUrl == null || adUrl.equals("")) {
        shouldAutoPlay = true;
        initPlayer();
    } else {
        shouldAutoPlay = false;
        requestAds(adUrl);
    }

    // When Play is clicked, request ads and hide the button.
    /*mPlayButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            mVideoPlayer.setVideoPath(getString(R.string.content_url));
            requestAds(getString(R.string.ad_tag_url));
            view.setVisibility(View.GONE);
        }
    });*/
}

private void requestAds(String adTagUrl) {
    AdDisplayContainer adDisplayContainer = mSdkFactory.createAdDisplayContainer();
    adDisplayContainer.setAdContainer(simpleExoPlayerView);

    // Create the ads request.
    AdsRequest request = mSdkFactory.createAdsRequest();
    request.setAdTagUrl(adTagUrl);
    request.setAdDisplayContainer(adDisplayContainer);
    request.setContentProgressProvider(new ContentProgressProvider() {
        @Override
        public VideoProgressUpdate getContentProgress() {
            if (mIsAdDisplayed || player == null || player.getDuration() <= 0) {
                return VideoProgressUpdate.VIDEO_TIME_NOT_READY;
            }
            return new VideoProgressUpdate(player.getCurrentPosition(),
                    player.getDuration());
        }
    });

    // Request the ad. After the ad is loaded, onAdsManagerLoaded() will be called.
    mAdsLoader.requestAds(request);
}
}

我的布局xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:keepScreenOn="true"
android:orientation="vertical">

<!-- Player view -->
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
    android:id="@+id/player_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerInParent="true" />

<!-- Loader container -->
<RelativeLayout
    android:id="@+id/mediaPlayerLoader"
    android:layout_width="124dp"
    android:layout_height="124dp"
    android:layout_centerInParent="true">

    <com.mikhaellopez.circularimageview.CircularImageView
        android:layout_width="121dp"
        android:layout_height="121dp"
        android:layout_centerInParent="true"
        android:src="@drawable/ic_sport5_loader"
        app:civ_border="true" />

    <!-- Loader-->
    <fr.castorflex.android.circularprogressbar.CircularProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:indeterminate="true"
        app:cpb_color="@color/colorPrimary"
        app:cpb_max_sweep_angle="300"
        app:cpb_min_sweep_angle="10"
        app:cpb_stroke_width="4dp" />

</RelativeLayout>

<!-- Top bar -->
<LinearLayout
    android:id="@+id/topBar"
    android:layout_width="match_parent"
    android:layout_height="56dp"
    android:background="#88000000"
    android:orientation="horizontal" >

    <ImageButton
        android:id="@+id/videoBackBtn"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_margin="16dp"
        android:layout_gravity="center_vertical"
        android:background="@null"
        android:src="@drawable/hplib_ic_back" />

    <cambium.com.il.sport5.views.CustomTextView
        android:id="@+id/videoPlayerTitle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center|right"
        android:layout_gravity="center_vertical"
        android:text="אתר ערוץ הספורט - חדשות הספורט, תוצאות, תקצירים ושידורים - Sport5.co.il"
        android:textColor="#FFF"
        android:textStyle="italic"
        android:singleLine="true"
        android:ellipsize="marquee"
        android:marqueeRepeatLimit="marquee_forever"
        android:scrollHorizontally="true"
        android:focusable="true"
        android:focusableInTouchMode="true"/>



    <ImageView
        android:id="@+id/sport5Ic"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:padding="16dp"
        android:scaleType="centerCrop"
        android:src="@drawable/ic_sport5" />

</LinearLayout>

<!-- New player controls -->
<com.google.android.exoplayer2.ui.PlaybackControlView
    android:id="@+id/controls_root"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:background="#88000000">

    <Button
        android:id="@+id/retry_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Retry"
        android:visibility="gone"/>

</com.google.android.exoplayer2.ui.PlaybackControlView>

<!-- Old controls -->
<LinearLayout
    android:id="@+id/controls"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:layout_gravity="bottom"
    android:background="#88000000"
    android:orientation="horizontal"
    android:visibility="gone">

    <ImageButton
        android:id="@+id/btn_lock"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="20dp"
        android:layout_weight="0"
        android:background="@null"
        android:src="@drawable/hplib_ic_lock" />

    <View
        android:layout_width="0dp"
        android:layout_height="1dp"
        android:layout_gravity="bottom"
        android:layout_weight="1" />

    <ImageButton
        android:id="@+id/btn_settings"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:layout_gravity="center_vertical"
        android:layout_marginRight="20dp"
        android:layout_weight="0"
        android:background="@null"
        android:src="@drawable/hplib_ic_settings"
        android:text="Settings" />


</LinearLayout>

最后分享 link 一个显示问题的短视频:

https://1drv.ms/v/s!AkkKHWeBCU1WhZwAmz3YW1rlhyYTHA

谢谢

问题原来是设置宽度为 0dp,weightSum 为 1。 只是将文本视图包装在 RelativeLayout 而不是 LinearLayout 中,一切都按预期工作。