首页 > 编程技术 > android

Android自定义WebView网络视频播放控件例子

发布时间:2016-10-2 16:23

下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。

因为业务需要,以下代码均以Youtube网站在线视频为例

实现功能:

1、初始化的时候显示标题和视频封面

2、初始化的时候显示一个play按钮

3、不需要依赖任何SDK,或者导入任何第三方库

4、播放过程中可以暂停,可以拖动进度条快进

5、可以全屏播放

6、切换页面的时候会自动暂停

7、页面退出的时候自动销毁WebView

8、不需要申请任何开发者账号或者获取授权


原理:

首先需要一个继承WebView的自定义控件,这里起名叫做YoutubePlayerView,在页面初始化的时候用这个WebView去加载一个事先写好的HTML,当然在加载之前,需要把Youtube的视频id和一些播放参数设置进去。然后一个小的播放窗口就完成了,此时已经完成用户点击play按钮就播放的功能。

但是光能播放还不行,我们还需要捕捉用户的点击事件,比如播放,暂停等等操作,而这些操作本身写在Youtube的JS代码中(Youtube已经把JS调用相关代码的位置预留好,就等着开发者来复写相关的代码了),需要在JS代码中调用java代码,这样就需要有一个JS调用java的接口,这里起名叫QualsonBridge,通过使用WebVIew的addJavascriptInterface()方法将Java代码的接口设置进去,并且需要一个接口实现类,实现的方法名称方法要和JS接口规定的方法一模一样,以便反射调用,一会会把详细的代码贴出来。

完成以上两点,就已经完成了播放,暂停等操作,但是还需要在Activity退出或者被覆盖的时候暂停WebView的播放,所以还需要给这个WebView写一个onDestroy的方法,并在fragment的onDestroy中调用,里面执行的主要就是清楚缓存的操作,还需要WebView写一个onPause的方法,在fragment的onPause中调用,里面主要执行JS代码:javascript:onVideoPause()

关于全屏播放:是通过一个自定义的WebChromeClient来实现将WebView扩大到全屏并修改旋转角度进行播放

代码实现:

首先需要让WebView去加载一块HTML,这段HTML是从Youtube的官方SDK中抽取出来的,本质上是一个HTML编写的小播放窗口,代码如下

 

 代码如下 复制代码

<!DOCTYPE html>
<html>
<style type="text/css">
  html, body {
     height:100%;
     width:100%;
     margin: 0;
     padding: 0;
     background:[BG_COLOR];
     overflow:hidden;
     position:relative;
  }
</style>
<script type = "text/javascript" src = "http://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script type = "text/javascript" src = "https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/jquery-ui.min.js"></script>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
<script" width=100% src="https://www.youtube.com/iframe_api"></script>
</head>
<body>
  <div id="QPlayer"></div>
</body>
<script type="text/javascript">
  var player;
  function onYouTubeIframeAPIReady() {
      player = new YT.Player('QPlayer', {
      height: '100%',
      width: '100%',
      videoId: '[VIDEO_ID]',
      events: {
        'onReady': onPlayerReady,
        'onStateChange': onPlayerStateChange,
        'onPlaybackQualityChange': onPlayerPlaybackQualityChange,
        'onPlaybackRateChange': onPlayerPlaybackRateChange,
        'onError': onPlayerError,
        'onApiChange': onPlayerApiChange
      },
      playerVars: {
        'autoplay': [AUTO_PLAY],
        'autohide':[AUTO_HIDE],
        'rel': [REL],
        'showinfo': [SHOW_INFO],
        'enablejsapi': [ENABLE_JS_API],
        'disablekb': [DISABLE_KB],
        'cc_lang_pref': '[CC_LANG_PREF]',
        'controls': [CONTROLS],
  'fs' : [FS],
  'origin' : 'https://www.youtube.com'
      }
    });
  }

  function onPlayerReady(event) {
    console.log('player is ready');
    onReady('player is ready');
    sendDuration();
    player.setOption("captions", "track", {"languageCode": "es"});
    player.unloadModule("captions");
  }

  var timerId = 0;
  function onPlayerStateChange(event) {
    clearTimeout(timerId);
    switch (event.data) {
      case YT.PlayerState.UNSTARTED:
        onStateChange("UNSTARTED");
        break;
      case YT.PlayerState.ENDED:
        onStateChange("ENDED");
        break;
      case YT.PlayerState.PLAYING:
        player.unloadModule("captions");
        onStateChange("PLAYING");
        timerId = setInterval(function() {
          setCurrentSeconds();
        }, 100);
        break;
      case YT.PlayerState.PAUSED:
        onStateChange("PAUSED");
        break;
      case YT.PlayerState.BUFFERING:
        onStateChange("BUFFERING");
        break;
      case YT.PlayerState.CUED:
        onStateChange("CUED");
        break;
    }
  }
//底下的这些function就是调用java代码的接口函数
  function onPlayerPlaybackQualityChange(playbackQuality) {
   console.log('playback quality changed to ' + playbackQuality.data);
   onPlaybackQualityChange('playback quality changed to ' + playbackQuality.data);
  }

  function onPlayerPlaybackRateChange(playbackRate) {
   console.log('playback rate changed to ' + playbackRate.data);
   onPlaybackRateChange('playback rate changed to ' + playbackRate.data);
  }

  function onPlayerError(e) {
   console.log('An error occurred: ' + e.data);
   onError('An error occurred: ' + e.data);
  }

  function onPlayerApiChange() {
   console.log('The player API changed');
   onApiChange('The player API changed');
  }

  function onReady(e){
    window.QualsonInterface.onReady(e);
  }
//这个函数是最重要的,用于通知Android代码播放状态改变
  function onStateChange(e){
    window.QualsonInterface.onStateChange(e);
  }

  function onPlaybackQualityChange(e){
    window.QualsonInterface.onPlaybackQualityChange(e);
  }

  function onPlaybackRateChange(e){
    window.QualsonInterface.onPlaybackRateChange(e);
  }

  function onError(e){
    window.QualsonInterface.onError(e);
  }

  function onApiChange(e){
    window.QualsonInterface.onApiChange(e);
  }

  function setCurrentSeconds(){
    window.QualsonInterface.currentSeconds(player.getCurrentTime());
  }

  function sendDuration(){
    window.QualsonInterface.duration(player.getDuration());
  }

  function setLog(msg){
    window.QualsonInterface.logs(msg);
  }

  function onSeekTo(startSeconds){
    player.seekTo(startSeconds, true)
  }

  function onVideoPause(){
    player.pauseVideo();
  }

  function onVideoStop(){
    player.stopVideo();
  }

  function onVideoPlay(){
    player.playVideo();
  }

  function onHideControls(){
    setLog("onHideControls()");
  }

  function loadVideo(videoId, startSeconds){
    setLog(videoId + "_" + startSeconds);
    player.loadVideoById(videoId, startSeconds);
  }

  function cueVideo(videoId){
    setLog(videoId);
    player.cueVideoById(videoId, 0, "default");
    player.setVolume(100)
  }
</script>
</html>

项目中把这一段代码放到了raw文件夹下,并通过下面这样一个方法去加载,并在加载的同时,把上面预留的一些参数比如视频id什么的给补上

 

 代码如下 复制代码

/**
     * 自己写一段HTML,并设置好Youtube的视频id,放到WebView中进行显示
     * @param videoId
     * @return
     */
    private String getVideoHTML(String videoId) {
        try {
            InputStream in = getResources().openRawResource(R.raw.players);
            if (in != null) {
                InputStreamReader stream = new InputStreamReader(in, "utf-8");
                BufferedReader buffer = new BufferedReader(stream);
                String read;
                StringBuilder sb = new StringBuilder("");

                while ((read = buffer.readLine()) != null) {
                    sb.append(read + "\n");
                }

                in.close();

                String html = sb.toString().replace("[VIDEO_ID]", videoId).replace("[BG_COLOR]", backgroundColor);
                html = html.replace("[AUTO_PLAY]", String.valueOf(params.getAutoplay())).replace("[AUTO_HIDE]", String.valueOf(params.getAutohide())).replace("[REL]", String.valueOf(params.getRel())).replace("[SHOW_INFO]", String.valueOf(params.getShowinfo())).replace("[ENABLE_JS_API]", String.valueOf(params.getEnablejsapi())).replace("[DISABLE_KB]", String.valueOf(params.getDisablekb())).replace("[CC_LANG_PREF]", String.valueOf(params.getCc_lang_pref())).replace("[CONTROLS]", String.valueOf(params.getControls())).replace("[FS]", String.valueOf(params.getFs()));
                return html;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }这里面传来的videoId一般是从Youtube视频分享url中用正则解析出来的,比如:https://youtu.be/DdRwiH4mR0Q

DdRwiH4mR0Q就是videoId


还需要一个给JS代码调用java代码的接口,要复写上面html中events中和function中的所有方法,如下


/**
     * WEB TO APP Javascript的安卓接口,用于在安卓上部署JS代码,这里是将JS回调到Android中,让JS触发Java代码
     * 需要在JS代码合适地方调用这里面的方法,在js中有一个函数如下:
     * function onPlayerStateChange(event)
     * 和这样一个函数
     * function onStateChange(e){
            window.QualsonInterface.onStateChange(e);//用于回调java代码
        }
     并且这个需要在java代码中使用 this.addJavascriptInterface(bridge, "QualsonInterface");来注册
     */
    private class QualsonBridge {

        @JavascriptInterface
        public void onReady(String arg) {
            JLogUtils.d(TAG, "onReady(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onReady();
            }
        }

        @JavascriptInterface
        public void onStateChange(String arg) {
            JLogUtils.d(TAG, "onStateChange(" + arg + ")");
            if ("UNSTARTED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.UNSTARTED);
            } else if ("ENDED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.ENDED);
            } else if ("PLAYING".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.PLAYING);
            } else if ("PAUSED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.PAUSED);
            } else if ("BUFFERING".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.BUFFERING);
            } else if ("CUED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.CUED);
            }
        }

        @JavascriptInterface
        public void onPlaybackQualityChange(String arg) {
            JLogUtils.d(TAG, "onPlaybackQualityChange(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onPlaybackQualityChange(arg);
            }
        }

        @JavascriptInterface
        public void onPlaybackRateChange(String arg) {
            JLogUtils.d(TAG, "onPlaybackRateChange(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onPlaybackRateChange(arg);
            }
        }

        @JavascriptInterface
        public void onError(String arg) {
            JLogUtils.e(TAG, "onError(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onError(arg);
            }
        }

        @JavascriptInterface
        public void onApiChange(String arg) {
            JLogUtils.d(TAG, "onApiChange(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onApiChange(arg);
            }
        }

        @JavascriptInterface
        public void currentSeconds(String seconds) {
            if (youTubeListener != null) {
                youTubeListener.onCurrentSecond(Double.parseDouble(seconds));
            }
        }

        @JavascriptInterface
        public void duration(String seconds) {
            if (youTubeListener != null) {
                youTubeListener.onDuration(Double.parseDouble(seconds));
            }
        }

        @JavascriptInterface
        public void logs(String arg) {
            JLogUtils.d(TAG, "logs(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.logs(arg);
            }
        }
    }


向js注册这个JAVA接口使用WebView的addJavascriptInterface(bridge, "QualsonInterface");方法

这里面的youTubeListener是自己写的一个接口类,用于方便回调UI方法的


下面贴出这个自定义WebView的完整代码:

 代码如下 复制代码

package com.imaginato.qravedconsumer.widget.player;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import com.qraved.app.R;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;

public class YoutubePlayerView extends WebView {

    private static final String TAG = YoutubePlayerView.class.getSimpleName();

    private QualsonBridge bridge = new QualsonBridge();

    private YTParams params = new YTParams();

    private YouTubeListener youTubeListener;
    private String backgroundColor = "#000000";
    private STATE mPlayState = STATE.UNSTARTED;

    public YoutubePlayerView(Context context) {
        super(context);
        setWebViewClient(new MyWebViewClient((Activity) context));
    }

    public YoutubePlayerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setWebViewClient(new MyWebViewClient((Activity) context));
    }

    @SuppressLint("JavascriptInterface")
    public void initialize(String videoId, YouTubeListener youTubeListener, WebChromeClient webChromeClient) {
        WebSettings set = this.getSettings();
        set.setJavaScriptEnabled(true);
        set.setUseWideViewPort(true);
        set.setLoadWithOverviewMode(true);
        set.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
        set.setCacheMode(WebSettings.LOAD_NO_CACHE);
        set.setPluginState(WebSettings.PluginState.ON);
        set.setPluginState(WebSettings.PluginState.ON_DEMAND);
        set.setAllowContentAccess(true);
        set.setAllowFileAccess(true);

        if (webChromeClient != null) {
            this.setWebChromeClient(webChromeClient);
        }

        this.mPlayState = STATE.UNSTARTED;
        this.youTubeListener = youTubeListener;
        this.setLayerType(View.LAYER_TYPE_NONE, null);
        this.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
        this.addJavascriptInterface(bridge, "QualsonInterface");//注册js代码调用java代码的接口
        this.loadDataWithBaseURL("https://www.youtube.com", getVideoHTML(videoId), "text/html", "utf-8", null);
        this.setLongClickable(true);
        this.setOnLongClickListener(new OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                return true;
            }
        });

    }

    public void initialize(String videoId, YTParams params, YouTubeListener youTubeListener, WebChromeClient webChromeClient) {
        if (params != null) {
            this.params = params;
        }
        initialize(videoId, youTubeListener, webChromeClient);
    }

    public void setWhiteBackgroundColor() {
        backgroundColor = "#ffffff";
    }

    public void setAutoPlayerHeight(Context context) {
        DisplayMetrics displayMetrics = new DisplayMetrics();
        ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        this.getLayoutParams().height = (int) (displayMetrics.widthPixels * 0.5625);
    }

    /**
     * 让WebView去执行JS代码javascript:onVideoPause(),来暂停视频
     */
    public void pause() {
        Log.d(TAG, "pause");
        this.loadUrl("javascript:onVideoPause()");
    }
    /**
     * 让WebView去执行JS代码,来停止视频
     */
    public void stop(){
        Log.d(TAG,"stop");
        this.loadUrl("javascript:onVideoStop()");
    }

    public STATE getPlayerState(){
        Log.d(TAG,"getPlayerState");
        return mPlayState;
    }

    public void play() {
        Log.d(TAG, "play");
        this.loadUrl("javascript:onVideoPlay()");
    }

    private void notifyStateChange(STATE state){
        if(youTubeListener!=null){
            youTubeListener.onStateChange(state);
        }
        this.mPlayState = state;
    }

    /**
     * WEB TO APP Javascript的安卓接口,用于在安卓上部署JS代码,这里是将JS回调到Android中,让JS触发Java代码
     * 需要在JS代码合适地方调用这里面的方法,在js中有一个函数如下:
     * function onPlayerStateChange(event)
     * 和这样一个函数
     * function onStateChange(e){
            window.QualsonInterface.onStateChange(e);//用于回调java代码
        }
     并且这个需要在java代码中使用 this.addJavascriptInterface(bridge, "QualsonInterface");来注册
     */
    private class QualsonBridge {

        @JavascriptInterface
        public void onReady(String arg) {
            Log.d(TAG, "onReady(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onReady();
            }
        }

        @JavascriptInterface
        public void onStateChange(String arg) {
            Log.d(TAG, "onStateChange(" + arg + ")");
            if ("UNSTARTED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.UNSTARTED);
            } else if ("ENDED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.ENDED);
            } else if ("PLAYING".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.PLAYING);
            } else if ("PAUSED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.PAUSED);
            } else if ("BUFFERING".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.BUFFERING);
            } else if ("CUED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.CUED);
            }
        }

        @JavascriptInterface
        public void onPlaybackQualityChange(String arg) {
            Log.d(TAG, "onPlaybackQualityChange(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onPlaybackQualityChange(arg);
            }
        }

        @JavascriptInterface
        public void onPlaybackRateChange(String arg) {
            Log.d(TAG, "onPlaybackRateChange(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onPlaybackRateChange(arg);
            }
        }

        @JavascriptInterface
        public void onError(String arg) {
            Log.e(TAG, "onError(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onError(arg);
            }
        }

        @JavascriptInterface
        public void onApiChange(String arg) {
            Log.d(TAG, "onApiChange(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onApiChange(arg);
            }
        }

        @JavascriptInterface
        public void currentSeconds(String seconds) {
            if (youTubeListener != null) {
                youTubeListener.onCurrentSecond(Double.parseDouble(seconds));
            }
        }

        @JavascriptInterface
        public void duration(String seconds) {
            if (youTubeListener != null) {
                youTubeListener.onDuration(Double.parseDouble(seconds));
            }
        }

        @JavascriptInterface
        public void logs(String arg) {
            Log.d(TAG, "logs(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.logs(arg);
            }
        }
    }


    /**
     * NonLeakingWebView
     */
    private static Field sConfigCallback;

    static {
        try {
            sConfigCallback = Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback");
            sConfigCallback.setAccessible(true);
        } catch (Exception e) {
            // ignored
        }
    }

    public void onDestroy() {
        super.onDetachedFromWindow();
        // View is now detached, and about to be destroyed
        youTubeListener = null;
        this.clearCache(true);
        this.clearHistory();
        try {
            if (sConfigCallback != null)
                sConfigCallback.set(null, null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private class MyWebViewClient extends WebViewClient {
        protected WeakReference<Activity> activityRef;

        public MyWebViewClient(Activity activity) {
            this.activityRef = new WeakReference<Activity>(activity);
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            try {
                final Activity activity = activityRef.get();
                if (activity != null)
                    activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
            } catch (RuntimeException ignored) {
                // ignore any url parsing exceptions
            }
            return true;
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
            Log.d(TAG, "onPageFinished()");
        }
    }

    public interface YouTubeListener {
        void onReady();//可以显示播放按钮进行播放

        void onStateChange(STATE state);//暂停等等状态

        void onPlaybackQualityChange(String arg);//清晰度改变

        void onPlaybackRateChange(String arg);

        void onError(String arg);

        void onApiChange(String arg);

        void onCurrentSecond(double second);

        void onDuration(double duration);

        void logs(String log);
    }

    public enum STATE {
        UNSTARTED,
        ENDED,
        PLAYING,
        PAUSED,
        BUFFERING,
        CUED,
        NONE
    }

    /**
     * 自己写一段HTML,并设置好Youtube的视频id,放到WebView中进行显示
     * @param videoId
     * @return
     */
    private String getVideoHTML(String videoId) {
        try {
            InputStream in = getResources().openRawResource(R.raw.players);
            if (in != null) {
                InputStreamReader stream = new InputStreamReader(in, "utf-8");
                BufferedReader buffer = new BufferedReader(stream);
                String read;
                StringBuilder sb = new StringBuilder("");

                while ((read = buffer.readLine()) != null) {
                    sb.append(read + "\n");
                }

                in.close();

                String html = sb.toString().replace("[VIDEO_ID]", videoId).replace("[BG_COLOR]", backgroundColor);
                html = html.replace("[AUTO_PLAY]", String.valueOf(params.getAutoplay())).replace("[AUTO_HIDE]", String.valueOf(params.getAutohide())).replace("[REL]", String.valueOf(params.getRel())).replace("[SHOW_INFO]", String.valueOf(params.getShowinfo())).replace("[ENABLE_JS_API]", String.valueOf(params.getEnablejsapi())).replace("[DISABLE_KB]", String.valueOf(params.getDisablekb())).replace("[CC_LANG_PREF]", String.valueOf(params.getCc_lang_pref())).replace("[CONTROLS]", String.valueOf(params.getControls())).replace("[FS]", String.valueOf(params.getFs()));
                return html;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
}

在Fragment中初始化的代码

 

View youtubeView = LayoutInflater.from(journalActivity).inflate(R.layout.layout_youtube_player, null);
        YoutubePlayerView youtubePlayerView = (YoutubePlayerView) youtubeView.findViewById(R.id.youtubePlayerView);
        youtubePlayerView.setAutoPlayerHeight(journalActivity);
        youtubePlayerView.initialize(videoID, new YoutubePlayerCallBack(youtubePlayerView), mWebChromeClient);
        ll_journal.addView(youtubeView,ll_journal.getChildCount()-1);
上面提到的布局文件R.layout.layout_youtube_player如下


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

    <com.xxx.YoutubePlayerView
        android:id="@+id/youtubePlayerView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="15dp"
        android:layout_marginRight="15dp"
        android:layout_marginTop="10dp"/>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_gravity="top"
        android:clickable="true"
        android:layout_marginLeft="15dp"
        android:layout_marginRight="15dp"
        android:layout_marginTop="10dp">
    </FrameLayout>

</FrameLayout>
上面提到的WebChromeClient定义如下,用于控制全屏播放的


private WebChromeClient mWebChromeClient = new WebChromeClient(){

        @Override
        public View getVideoLoadingProgressView() {
                LayoutInflater inflater = LayoutInflater.from(activity);
                mVideoProgressView = inflater.inflate(R.layout.video_layout_loading, null);
           
            return mVideoProgressView;
        }

        @Override
        public void onShowCustomView(View view,
                                     WebChromeClient.CustomViewCallback callback) {
            // if a view already exists then immediately terminate the new one
            if(journalActivity==null){
                return;
            }
            if (mCustomView != null) {
                onHideCustomView();
                return;
            }

            // 1. Stash the current state
            mCustomView = view;
            mOriginalSystemUiVisibility = journalActivity.getWindow().getDecorView().getSystemUiVisibility();
            mOriginalOrientation = journalActivity.getRequestedOrientation();

            // 2. Stash the custom view callback
            mCustomViewCallback = callback;

            // 3. Add the custom view to the view hierarchy
            FrameLayout decor = (FrameLayout) journalActivity.getWindow().getDecorView();
            decor.addView(mCustomView, new FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT));
            if(mVideoFullScreenBack!=null){
                mVideoFullScreenBack.setVisibility(View.VISIBLE);
            }

            // 4. Change the state of the window
            activity.getWindow().getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                            View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
                            View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
                            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
                            View.SYSTEM_UI_FLAG_FULLSCREEN |
                            View.SYSTEM_UI_FLAG_IMMERSIVE);
            activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        }

        @Override
        public void onHideCustomView() {
            if (journalActivity == null) {
                return;
            }
            // 1. Remove the custom view
            FrameLayout decor = (FrameLayout) journalActivity.getWindow().getDecorView();
            decor.removeView(mCustomView);
            mCustomView = null;
            if(mVideoFullScreenBack!=null){
                mVideoFullScreenBack.setVisibility(View.GONE);
            }

            // 2. Restore the state to it's original form
            journalActivity.getWindow().getDecorView()
                    .setSystemUiVisibility(mOriginalSystemUiVisibility);
            journalActivity.setRequestedOrientation(mOriginalOrientation);

            // 3. Call the custom view callback
            if(mCustomViewCallback!=null){
                mCustomViewCallback.onCustomViewHidden();
                mCustomViewCallback = null;
            }

        }
    };

上面提到的R.layout.view_layout_loading布局文件如下,仅仅是一个progressBar当占位符用的


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

    <ProgressBar
        android:id="@android:id/progress"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"/>

</FrameLayout>
从url中抽取VideoId的方法如下


 private String parseIDfromVideoUrl(String videoUrl){
       
        int startIndex = videoUrl.indexOf(VIDEO_ID_START);
        if(startIndex != -1){
            startIndex = startIndex + VIDEO_ID_START.length();
            int endIndex = videoUrl.indexOf("?");
            if(endIndex == -1){
                endIndex = videoUrl.length();
            }
            if(startIndex < endIndex){
                return videoUrl.substring(startIndex,endIndex);
            }
        }
        return "";
    }
因为本项目在同一个fragment中放了好多的这样的视频播放控件,所以为了统一他们暂停,销毁操作,这里使用了一个ArrayList进行维护

当切换到其他fragment或者有新的Activity压到上面的时候暂停WebView的播放,fragment总的onPause方法这么写:


@Override
    public void onPause() {
        if(playerViewList!=null){
            for(YoutubePlayerView v : playerViewList){
                if(v.getPlayerState() == YoutubePlayerView.STATE.PLAYING ){
                    v.pause();
                }else if(v.getPlayerState() == YoutubePlayerView.STATE.BUFFERING){
                    v.stop();
                }
            }
        }
        super.onPause();
    }还需要让fragment在销毁的时候释放WebView的资源如下:


@Override
    public void onDestroy() {
        super.onDestroy();

        if(playerViewList!=null){
            for(YoutubePlayerView v : playerViewList){
                if(v!=null){
                    v.onDestroy();
                }
            }
        }
}
在按下返回按钮时关闭全屏显示的代码


 @Override
    public void onBackPressed() {
      
                        boolean isClose = currentJournalFragment.closeFullScreen();
                        if(isClose){
                            return;
                        }
        
            }这个fragment的closeFullScreen方法如下


public boolean closeFullScreen(){
        if(mCustomView!=null && mCustomViewCallback!=null){
            mWebChromeClient.onHideCustomView();
            return true;
        }
        return false;
    }

这里面的mCustomViewCallback 是WebChromeClient的onShowCustomView()方法的第二个参数,使用一个全局变量把它引用起来

下面我们来看一篇关于Android 开发之布局细节对比:RTL模式 ,希望这篇文章对各位同学会带来帮助,具体的细节如下介绍。

前言

讲真,好久没写博客了,2016都过了一半了,赶紧重新捡起来。(个人感慨,和内容无关……
所谓RTL,顾名思义也就是Right To Left,是一种阿拉伯语、波斯语等情况下从右往左的阅读方式。当所开发的App等面向海外用户时需要做这个适配。

从Android 4.2开始支持原生的RTL模式,对此之前的版本我也不想多做说明,这些老版本要是还支持那Android碎片化就没完没了了。

正文

如何查看效果

首先要说的是,想要看RTL模式,不必去把手机中的语言/国家设置到阿拉伯等,只需要在“开发者选项”中勾选“强制使用从右到左的布局方向“,这样真的是方便太多了。

此处以MIUI为例,大家也不妨自己动手试试。

如图,原本左右两侧的控件发生了对调,值得注意的是图中红色方框标注的图标发生了翻转(更标准的说法是”镜像“)。

如何支持RTL

是不是很有意思呢,那么如果在你的App中适配RTL呢?

1,需要在清单文件总队RTL的支持做一个声明,放到< application >节点下。

android:supportsRtl="true"
2,将布局中的”left、right“相关的属性换成对应的”start、end“属性。

这一步可能用说的不够清晰,看代码看图!

    <Button
        android:id="@+id/button"
        android:text="A"
        android:layout_width="60dp"
        android:layout_height="40dp" />

    <Button
        android:id="@+id/button2"
        android:text="B"
        android:layout_toRightOf="@id/button"
        android:layout_width="60dp"
        android:layout_height="40dp" />

    <Button
        android:id="@+id/button3"
        android:text="C"
        android:layout_toRightOf="@id/button2"
        android:layout_width="60dp"
        android:layout_height="40dp" />

应该可以看出来这是在一个相对布局中,默认情况下是这样的:

开启RTL后,却是这样的:

为什么B、C按钮不见了?因为根据属性,它们都在A的右边,这已经超出的屏幕边界。

如果我们对布局做一点修改:

android:layout_toRightOf
改成
android:layout_toEndOf

如果有left,也照搬改成start就好。

页面不想支持RTL怎么办

有一些界面你不想它支持RTL,或者它本身不需要支持,那又该如何呢?比如说拨号界面,难道要把数字键也镜像过去吗:

只需要加上这么一句就好了呀。

layoutDirect可以使用4种属性:
ltr:从左往右
rtl:从右往左
inherit:从上层视图中继承
locale:由Locale决定

分别对应的int值为0,1,2,3。
图片怎么办
只需要创建一个文件夹,把镜像后的图片放进去即可,代码中不用做任何修改。

drawable-ldrtl-xhdpi
drawable-xhdpi

分辨率是一一对应的


当然了,除了对图像做预处理外,要是想用代码直接控制也是可以的。

private ImageView image2;

// 省略

image2 =  (ImageView) findViewById(R.id.image2);

Drawable arrow = getResources().getDrawable(R.drawable.arrow);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    if (arrow != null) {
       arrow.setAutoMirrored(true);
    }
}
image2.setImageDrawable(arrow);

Android Webview是可以加载本地资源了,这个功能在开发时肯定不是为了加载广告了,现在在安卓开发中Android Webview通常会用来加载广告了,下面我们来看看如何过滤广告吧。

现在大部分的android平台的浏览器都具有广告过滤的功能,同时大部分网站都有广告。广告行业是个盈利巨大的产业,就我看来,现在绝大部分的互联网产品,一般有两种营利模式:免费有广告和会员无广告;他们大部分(包括网站和APP等)盈利来源就是广告。作为用户而言,对广告十分反感,但是互联网上的服务我们能免费享受与支撑这些服务得以延续与成长却得益于广告。

优酷广告

百度了一下,网上竟然没有Android浏览器屏蔽广告的代码实现,所以极客人只能自己动手了。网页上的广告一般是站长在网页植入一段js代码,要想屏蔽广告只需要将这些js屏蔽掉即可。

WebViewClient的几个回调函数

要想对Webview实现一些高级操作,首先要学习WebViewClient的用法,这是Webview几个常用回调函数

1、public boolean shouldOverrideUrlLoading(WebView view, String url) : 在点击请求的是链接是才会调用,重写此方法返回true表明点击网页里面的链接还是在当前的webview里跳转,不跳到浏览器那边。
2、public void onReceivedSslError(WebView view, SslErrorHandler handler, android.net.http.SslError error):
重写此方法可以让webview处理https请求。
3、public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event):重写此方法才能够处理在浏览器中的按键事件。
4、 public void onLoadResource(WebView view, String url) :在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次。
5、 public void onPageStarted(WebView view, String url, Bitmap favicon) :在页面加载开始时调用。
6、public void onPageFinished(WebView view, String url) :在页面加载结束时调用。
初看上面的回调函数,极客人发现了一个巨大的坑,拦截广告就是拦截加载广告的js,上面的onLoadResource似乎是很合适的函数,只要判断onLoadResource的参数url是否是加载广告js的即可,如果不是广告相关的url正常加载,如果是则不加载。但是在使用onLoadResource之后才发现根本不行。

这里引用WebViewClient另外一个回调函数:public WebResourceResponse shouldInterceptRequest(WebView view, String url)

shouldInterceptRequest有两种重载。

public WebResourceResponse shouldInterceptRequest (WebView view, String url) 从API 11开始引入,API 21弃用
public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request) 从API 21开始引入
这里极客人暂且使用shouldInterceptRequest (WebView view, String url)完成对webview广告的拦截。

拦截广告资源URL

在Webview加载资源时会回调shouldInterceptRequest函数,我们可以通过重写shouldInterceptRequest函数实现对webview的资源请求进行处理。进行处理后返回数据。如果主程序返回的数据为null,WebView会自行请求网络加载资源。这里有个坑:不是shouldInterceptRequest函数返回null就能屏蔽掉请求!正确的屏蔽请求的方式:

@Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
        url = url.toLowerCase();
 if (!ADFilterTool.hasAd(context, url)) {
                return super.shouldInterceptRequest(view, url);//正常加载
            }else{
                return new WebResourceResponse(null,null,null);//含有广告资源屏蔽请求
            }
}

下面是极客人写的屏蔽广告的NoAdWebViewClient类: 只需使用webview.setWebViewClient(NoAdWebViewClient webclient)即可屏蔽指定webview的广告。

NoAdWebViewClient 屏蔽广告

package cn.wangbaiyuan.webviewadblock;
 
import android.content.Context;
import android.util.Log;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
 
/**
 * Created by BrainWang on 05/01/2016.
 */
public class NoAdWebViewClient extends WebViewClient {
    private  String homeurl;
    private Context context;
 
    public NoAdWebViewClient(Context context,String homeurl) {
        this.context = context;
        this.homeurl = homeurl;
    }
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
        url = url.toLowerCase();
        if(!url.contains(homeurl)){
            if (!ADFilterTool.hasAd(context, url)) {
                return super.shouldInterceptRequest(view, url);
            }else{
                return new WebResourceResponse(null,null,null);
            }
        }else{
            return super.shouldInterceptRequest(view, url);
        }
 
 
    }
}

判断URL是否含广告的ADFilterTool类:该类通过判断url是否包含在广告拦截库中

package cn.wangbaiyuan.webviewadblock;
 
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
 
/**
 * Created by BrainWang on 05/01/2016.
 */
public class ADFilterTool {
    public static boolean hasAd(Context context, String url) {
        Resources res = context.getResources();
        String[] adUrls = res.getStringArray(R.array.adBlockUrl);
        for (String adUrl : adUrls) {
            if (url.contains(adUrl)) {
                return true;
            }
        }
        return false;
    }
}

广告Url资源文件(广告拦截库可自行百度更新):AdUrlString.Xml

所谓广告拦截库,实际上是请求广告资源的url合集,网络上有大量的广告拦截库,读者可以定期更新一下文件来实现对广告的高效过滤。本文屏蔽的方式比较粗暴,凡是含有广告资源的域名统统禁止。要想实现更精准的过滤,访友你可以使用通配符匹配url的方式进行拦截,现在PC端的浏览器正是这样做的。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="adBlockUrl">
        <item>ubmcmm.baidustatic.com</item>
        <item>cpro2.baidustatic.com</item>
        <item>cpro.baidustatic.com</item>
        <item>s.lianmeng.360.cn</item>
        <item>nsclick.baidu.com</item>
        <item>pos.baidu.com</item>
        <item>cbjs.baidu.com</item>
        <item>cpro.baidu.com</item>
        <item>images.sohu.com/cs/jsfile/js/c.js</item>
        <item>union.sogou.com/</item>
        <item>sogou.com/</item>
        <item>a.baidu.com</item>
        <item>c.baidu.com</item>
 
    </string-array>
</resources>
 

下面我们来看一篇关于安卓开发之Scroller.startScroll()方法简介,希望这篇文章能够让各位深入理解到Scroller.startScroll方法的用法。

上篇说到,可以使用scrollBy和scrollTo移动View,这里产生一个问题,加入一个View在屏幕的左上角即(0,0)位置,他调用

scrollTo(-300,-300)时,会立即跳过去,显得很突兀,如何让他平缓地划过去呢?就可以用本篇的方法了。


使用方法:

int startX;//滑动动作的起始点x坐标
int startY;//滑动动作的起始点y坐标
int dx;    //x轴偏移量向左为负,向右为正(即负值向右移,正值向左移)
int dy;    //y轴偏移量向左为负,向右为正(即负值向右移,正值向左移)
int duration;//时间,默认为250ms
注意:这里的dx和dy和上篇的scrollBy的参数相似,都是往哪个地方移动了多少,而不是往哪个点移动
Scroller mScroller=new Scroller(context);
mScroller.startScroll(startX,startY,dx,dy);
invalidate();//

除了上面的代码之外,还得重写View的computeScroll方法:

这里我的理解是:判断某一阶段滑动完成,就调用scrollTo方法,实现真正的移动

@Override
public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
        scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
        postInvalidate();
    }
    super.computeScroll();
}

可能大家还有点不明白,我还是来画图吧:

一个view,距离上边300px,距离左边300px,即他的坐标为(300,300);

那么这个时候,我们希望他从当前的这个位置,缓慢移动到(0,0),用时1秒,就这么写:

mScroller.startScroll(-300, -300, 300, 300,1000);

假设我们希望它从(300,300)移动到(100,100):

mScroller.startScroll(-300, -300, 200, 200,5000);

假设我们希望它从(300,300)移动到(200,200):

mScroller.startScroll(-300, -300, 100, 100,5000);

这样只是移动某段距离,那我们怎么样使它移动到一个固定的点呢?

我们可以这样做:假设我们想要移动到(100,100):


mScroller.startScroll(-300, -300, 0, 0, 500);
mScroller.setFinalX(100);
mScroller.setFinalY(100);
invalidate();

我为什么这样写呢?是有依据的,看一下startScroll的源码:

public void startScroll(int startX, int startY, int dx, int dy, int duration) {
    mMode = SCROLL_MODE;
    mFinished = false;
    mDuration = duration;
    mStartTime = AnimationUtils.currentAnimationTimeMillis();
    mStartX = startX;
    mStartY = startY;
    mFinalX = startX + dx;
    mFinalY = startY + dy;
    mDeltaX = dx;
    mDeltaY = dy;
    mDurationReciprocal = 1.0f / (float) mDuration;
}
 

其中:

mFinalX = startX + dx;
mFinalY = startY + dy;

就是把我们传进去的几个值加了一下,意思就是,我们传的dx,dy,就是我们移动的偏移量,受到起始数值的约束,

所以我们单独操控mFinalX、mFinalY就可以了.

最后解释一下computeScroll方法:


@Override
public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
        scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
        postInvalidate();
    }
    super.computeScroll();
}

首先把上面的例子拿来:

mScroller.startScroll(-300, -300, 300, 300,1000);
在(300,300)点,用时一秒,移动到(0,0),我们想一下,在一秒内完成这个操作,假设我们细分成100部分,即一部分为10ms,

那么在第一个10ms,    mScrollerd的getCurrX()就是-300+3=-270,getCurrX同理,即第一个10ms后,view需要向左边移动了3px,这个时候computeScroll调用了,我们先判断一下mScroller的第一个阶段是不是完成了,假设完成了,就调用scrollTo()方法。

这样经过100个部分,在我们看来,就是缓慢的移动过去了.

下面我们来看一篇关于安卓开发之scrollBy和scrollTo简述的教程希望这篇文章可以帮助到各位深入理解到scrollBy和scrollTo的用法吧。

刚刚在学习listview滑动删除,要用到view的scrollBy和scrollTo,就研究了一下,下面分享一下:

先说scrollTo,

scrollTo(int x, int y):传入x,y坐标,将view移动到(-x,-y)位置

scrollBy(int x, int y):传入x,y坐标,将view向x正方向移动-x个单位,向y正方向移动-y个单位

假设x为正数,就向左移,反之向右

下面画个图举个例子:

无标题

红线框代表手机界面,黑框代表一个LinearLayout,id设为llayout;

下面调用:

llayout.scrollTo(-100,0);

效果:

这个时候再调用llayout.scrollTo(-100,0);图像是不会发生变化的,因为这个方法的作用是移动到固定的点

那么,这个时候再调用:llayout.scrollBy(-100,0);

图像就会变成这样:

即向右移动了100px,

这个时候假设再调用llayout.scrollTo(0,0);他又会回到初始位置了

 看scrollBy的方法可以看出:

scrollBy也是调用了scrollTo的方法,而参数是当前的Scroll坐标加上传入的坐标,即从当前位置移动(x,y)


public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}

可能有的同学会问,为什么参数是负的,走的确是正的方向,这个问题在view的某一句源码可以得到解释:

public void invalidate(int l, int t, int r, int b) {
    final int scrollX = mScrollX;
    final int scrollY = mScrollY;
    invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
}
负负得正,这样容易理解多了吧,至于为什么要减,各位可以自己去探索

 
使用通俗话总结下这两个方法:

假如两个人在跑步,scrollTo(-50,0)即移动到第50米,scrollBy(-50,0)即移动50米,当两人位于起点时,两方法等效

标签:[!--infotagslink--]

您可能感兴趣的文章: