Android 开发之布局细节对比:RTL模式

 更新时间:2016年10月2日 16:23  点击:3070
下面我们来看一篇关于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);

下面小编为各位整理一些关于安卓开发ViewPager图片预览之图片的放大缩小,移动,切换的基本知识与例子,具体的如下介绍。

1,自由的放大和缩小

2.双击放大与缩小

3.放大以后可以进行自由的移动

4.处理与ViewPager之间的的事件冲突

需要用到的知识点

1.Matrix (图片放大,缩小需要用到矩阵)

2.ScaleGestureDetector(检测用户多指触控时缩放的手势)

3.GestureDetector:检测用户双击时需要做的一些处理

4.事件分发机制(当我们图片放大时,我们的图片是可以左右移动的,在ViewPager左右切换图片,两者会有冲突)。

 


----------------------------------------------------代码设计

第一课

第一步 :自定义ImageView 实现图片自适应控件大小:(效果是:图片小于控件大小时,放大到控件大小,图片大于控件大小时,自动缩小到控件大小)


package com.example.viewpagerimage;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;


//实现监听器OnGlobalLayoutListener,监听图片是否加载完成
public class MyImageView extends ImageView implements OnGlobalLayoutListener{

 private boolean mOnce;//判断是否初始化
 private float mInitScale;//初始化时缩放的值
 private float mMidScale;//双击放大到达的值
 private float mMaxScale;//放大的最大值

 private Matrix mScaleMatrix;
 public MyImageView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  //init
  mScaleMatrix = new Matrix();
  setScaleType(ScaleType.MATRIX);
  //当图片加载时,图片可能很大,也可能很小,需要让图片自适应屏幕大小,当图片太大时自动缩小到屏幕大小,当图片太小时放大到屏幕大小。

 }

 public MyImageView(Context context, AttributeSet attrs) {
  this(context, attrs,0);
  // TODO Auto-generated constructor stub
 }

 public MyImageView(Context context) {
  this(context,null);
  // TODO Auto-generated constructor stub
 }
 @Override
 protected void onAttachedToWindow() {
  // TODO Auto-generated method stub
  super.onAttachedToWindow();//当View 显示在屏幕上时调用
  getViewTreeObserver().addOnGlobalLayoutListener(this);//注册接口
 }
 @SuppressWarnings("deprecation")
 @Override
 protected void onDetachedFromWindow() {
  // TODO Auto-generated method stub
  super.onDetachedFromWindow();//当View从屏幕上移除时调用
  getViewTreeObserver().removeGlobalOnLayoutListener(this);//移除接口
 }
 /**
  * 获取ImageView加载完成的图片
  */
 @Override
 public void onGlobalLayout() {
  // 全局的布局完成后调用
  if(!mOnce){
   //得到控件的宽和高
   int width = getWidth();
   int height = getHeight();
   //得到我们的图片以及宽和高
   Drawable d = getDrawable();
   if(d == null)
    return;

   int dw = d.getIntrinsicWidth();
   int dh = d.getIntrinsicHeight();
   float scale = 1.0f;//缩放值
   //如果图片的宽度大于控件高度,但是宽度小于控件的宽度,将其缩小
   if(dw > width && dh < height){
    scale = width*1.0f/dw;
   }
   else if(dh > height && dw < width){
    scale = height*1.0f /dh;
   }

   else if(dw > width && dh > height){
    scale = Math.min(width*1.0f/dw, height*1.0f/dh);
   }
   else if(dw < width && dh < height){
    scale = Math.min(width *1.0f/dw, height*1.0f/dh);
   }
   /*
    * 得到初始化时缩放的比例
    * */

   mInitScale = scale;
   mMaxScale = mInitScale * 4;
   mMidScale = mInitScale * 2;

   //将图片移动到当前控件的中心
   int dx = getWidth()/2 - dw /2;
   int dy = getHeight()/2 - dh/2;

   mScaleMatrix.postTranslate(dx, dy);//平移
   mScaleMatrix.postScale(mInitScale, mInitScale,width/2,height/2);//缩放,后面两个参数是缩放的中心点
   setImageMatrix(mScaleMatrix);

   mOnce = true;

 

 

  }
 }
}

布局文件使用:


<LinearLayout 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"
    tools:context="com.example.viewpagerimage.MainActivity" >

    <com.example.viewpagerimage.MyImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="matrix"
        android:src="@drawable/viewpatherimage" />

</LinearLayout>


第二步:给自定义控件添加支持手指触控缩放的功能:(支持手指触控放大)

因为涉及到手势触摸事件所以要实现OnScaleGestureListener,OnTouchListener这两个接口。

声明成员变量: private ScaleGestureDetector mScaleGestureDetector;//捕获用户多指触控缩放的比例

在构造函数中初始化:

mScaleGestureDetector = new ScaleGestureDetector(context, this);
setOnTouchListener(this);

 

 

添加方法:


/**\
  * 获取当前图片的缩放值
  * @return
  */
 public float getScale(){
  float[] values = new float[9];
  mScaleMatrix.getValues(values);
  return values[Matrix.MSCALE_X];

 }

 

实现接口中的方法:

 

 

//缩放的区间,initScale maxScale
 @Override
 public boolean onScale(ScaleGestureDetector detector) {
  // TODO Auto-generated method stub
  float scale = getScale();
  float scaleFactor = detector.getScaleFactor();//得到缩放的值

  if(getDrawable() == null){
   return true;
  }
  //缩放范围的控制
  if((scale < mMaxScale && scaleFactor > 1.0f) || (scale > mInitScale && scaleFactor < 1.0f)){
   if(scale * scaleFactor < mInitScale){
    scaleFactor = mInitScale / scale;//当手指缩放小于最小值时 ,默认显示最小的比例
   }
   if(scale * scaleFactor > mMaxScale){//当手指缩放大于于最大值时 ,默认显示最大的比例
    scale = mMaxScale/scale;
   }
   //缩放
   mScaleMatrix.postScale(scaleFactor, scaleFactor, getWidth()/2, getHeight()/2);
   setImageMatrix(mScaleMatrix);
  }
  return true;//设置完成返回true保证事件能够进行
 }

 @Override
 public boolean onScaleBegin(ScaleGestureDetector detector) {
  // TODO Auto-generated method stub
  return true;//必须返回true
 }

 @Override
 public void onScaleEnd(ScaleGestureDetector detector) {
  // TODO Auto-generated method stub

 }

 @Override
 public boolean onTouch(View v, MotionEvent event) {
  // TODO Auto-generated method stub
  mScaleGestureDetector.onTouchEvent(event);//把event传递给mscaleGestureDetector处理
  return true;//必须返true
 }

 

 


全部代码如下:


package com.example.viewpagerimage;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;
import android.view.View.OnTouchListener;

//实现监听器OnGlobalLayoutListener,监听图片是否加载完成
public class MyImageView extends ImageView implements OnGlobalLayoutListener, OnScaleGestureListener,OnTouchListener{

 private boolean mOnce;//判断是否初始化
 private float mInitScale;//初始化时缩放的值
 private float mMidScale;//双击放大到达的值
 private float mMaxScale;//放大的最大值

 private ScaleGestureDetector mScaleGestureDetector;//捕获用户多指触控缩放的比例

 private Matrix mScaleMatrix;
 public MyImageView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  //init
  mScaleMatrix = new Matrix();
  setScaleType(ScaleType.MATRIX);

  mScaleGestureDetector = new ScaleGestureDetector(context, this);
  
  setOnTouchListener(this);
  //当图片加载时,图片可能很大,也可能很小,需要让图片自适应屏幕大小,当图片太大时自动缩小到屏幕大小,当图片太小时放大到屏幕大小。

 }

 public MyImageView(Context context, AttributeSet attrs) {
  this(context, attrs,0);
  // TODO Auto-generated constructor stub
 }

 public MyImageView(Context context) {
  this(context,null);
  // TODO Auto-generated constructor stub
 }
 @Override
 protected void onAttachedToWindow() {
  // TODO Auto-generated method stub
  super.onAttachedToWindow();//当View 显示在屏幕上时调用
  getViewTreeObserver().addOnGlobalLayoutListener(this);//注册接口
 }
 @SuppressWarnings("deprecation")
 @Override
 protected void onDetachedFromWindow() {
  // TODO Auto-generated method stub
  super.onDetachedFromWindow();//当View从屏幕上移除时调用
  getViewTreeObserver().removeGlobalOnLayoutListener(this);//移除接口
 }
 /**
  * 获取ImageView加载完成的图片
  */
 @Override
 public void onGlobalLayout() {
  // 全局的布局完成后调用
  if(!mOnce){
   //得到控件的宽和高
   int width = getWidth();
   int height = getHeight();
   //得到我们的图片以及宽和高
   Drawable d = getDrawable();
   if(d == null)
    return;

   int dw = d.getIntrinsicWidth();
   int dh = d.getIntrinsicHeight();
   float scale = 1.0f;//缩放值
   //如果图片的宽度大于控件高度,但是宽度小于控件的宽度,将其缩小
   if(dw > width && dh < height){
    scale = width*1.0f/dw;
   }
   else if(dh > height && dw < width){
    scale = height*1.0f /dh;
   }

   else if(dw > width && dh > height){
    scale = Math.min(width*1.0f/dw, height*1.0f/dh);
   }
   else if(dw < width && dh < height){
    scale = Math.min(width *1.0f/dw, height*1.0f/dh);
   }
   /*
    * 得到初始化时缩放的比例
    * */

   mInitScale = scale;
   mMaxScale = mInitScale * 4;
   mMidScale = mInitScale * 2;

   //将图片移动到当前控件的中心
   int dx = getWidth()/2 - dw /2;
   int dy = getHeight()/2 - dh/2;

   mScaleMatrix.postTranslate(dx, dy);//平移
   mScaleMatrix.postScale(mInitScale, mInitScale,width/2,height/2);//缩放,后面两个参数是缩放的中心点
   setImageMatrix(mScaleMatrix);

   mOnce = true;

 

 

  }
 }
 /**\
  * 获取当前图片的缩放值
  * @return
  */
 public float getScale(){
  float[] values = new float[9];
  mScaleMatrix.getValues(values);
  return values[Matrix.MSCALE_X];

 }
 //缩放的区间,initScale maxScale
 @Override
 public boolean onScale(ScaleGestureDetector detector) {
  // TODO Auto-generated method stub
  float scale = getScale();
  float scaleFactor = detector.getScaleFactor();//得到缩放的值

  if(getDrawable() == null){
   return true;
  }
  //缩放范围的控制
  if((scale < mMaxScale && scaleFactor > 1.0f) || (scale > mInitScale && scaleFactor < 1.0f)){
   if(scale * scaleFactor < mInitScale){
    scaleFactor = mInitScale / scale;//当手指缩放小于最小值时 ,默认显示最小的比例
   }
   if(scale * scaleFactor > mMaxScale){//当手指缩放大于于最大值时 ,默认显示最大的比例
    scale = mMaxScale/scale;
   }
   //缩放
   mScaleMatrix.postScale(scaleFactor, scaleFactor, getWidth()/2, getHeight()/2);
   setImageMatrix(mScaleMatrix);
  }
  return true;//设置完成返回true保证事件能够进行
 }

 @Override
 public boolean onScaleBegin(ScaleGestureDetector detector) {
  // TODO Auto-generated method stub
  return true;//必须返回true
 }

 @Override
 public void onScaleEnd(ScaleGestureDetector detector) {
  // TODO Auto-generated method stub

 }

 @Override
 public boolean onTouch(View v, MotionEvent event) {
  // TODO Auto-generated method stub
  mScaleGestureDetector.onTouchEvent(event);//把event传递给mscaleGestureDetector处理
  return true;//必须返true
 }
}

目前实现的效果:无论手指触摸哪里都是以中心点位中心开始缩放的。

下面实现的效果是:以手指触控的任意点为中心开始缩放

第三课(第三步):支持以手指触控的任意点为中心开始缩放

关键部分是在缩放的时候不断进行边界检测,防止放大后缩小后出现白边:


/**
  * 在缩放的时候进行边界控制范围位置控制
  */

 private void checkBorderAndCenterWhenScale() {
  // TODO Auto-generated method stub
  RectF rect = getMatrixRectF();
  float deltaX = 0;
  float deltaY = 0;

  float width = getWidth();
  float height = getHeight();
  //缩放时进行边界检测,放在出现白边
  if(rect.width() >= width){
   if(rect.left > 0){//处理左边的空白
    deltaX = -rect.left;
   }
   if(rect.right < width){//处理右边的空白
    deltaX = (int) (width - rect.right);
   }
  }
  if(rect.height() >= height){
   if(rect.top > 0){
    deltaY = -rect.top;
   }
   if(rect.bottom < height){
    deltaY = height - rect.bottom;
   }
  }
  //如果宽度或高度小于控件的宽或高,则让其居中
  if(rect.width() < width){
   deltaX = width/2f -rect.right + rect.width()/2f;
  }
  if(rect.height() < height){
   deltaY = height /2f -rect.bottom + rect.height()/2f;
  }
  mScaleMatrix.postTranslate(deltaX, deltaY);

 }

全部代码:


package com.example.viewpagerimage;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;

//实现监听器OnGlobalLayoutListener,监听图片是否加载完成
public class MyImageView extends ImageView implements OnGlobalLayoutListener, OnScaleGestureListener,OnTouchListener{

 private boolean mOnce;//判断是否初始化
 private float mInitScale;//初始化时缩放的值
 private float mMidScale;//双击放大到达的值
 private float mMaxScale;//放大的最大值

 private ScaleGestureDetector mScaleGestureDetector;//捕获用户多指触控缩放的比例

 private Matrix mScaleMatrix;
 public MyImageView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  //init
  mScaleMatrix = new Matrix();
  setScaleType(ScaleType.MATRIX);

  mScaleGestureDetector = new ScaleGestureDetector(context, this);

  setOnTouchListener(this);
  //当图片加载时,图片可能很大,也可能很小,需要让图片自适应屏幕大小,当图片太大时自动缩小到屏幕大小,当图片太小时放大到屏幕大小。

 }

 public MyImageView(Context context, AttributeSet attrs) {
  this(context, attrs,0);
  // TODO Auto-generated constructor stub
 }

 public MyImageView(Context context) {
  this(context,null);
  // TODO Auto-generated constructor stub
 }
 @Override
 protected void onAttachedToWindow() {
  // TODO Auto-generated method stub
  super.onAttachedToWindow();//当View 显示在屏幕上时调用
  getViewTreeObserver().addOnGlobalLayoutListener(this);//注册接口
 }
 @SuppressWarnings("deprecation")
 @Override
 protected void onDetachedFromWindow() {
  // TODO Auto-generated method stub
  super.onDetachedFromWindow();//当View从屏幕上移除时调用
  getViewTreeObserver().removeGlobalOnLayoutListener(this);//移除接口
 }
 /**
  * 获取ImageView加载完成的图片
  */
 @Override
 public void onGlobalLayout() {
  // 全局的布局完成后调用
  if(!mOnce){
   //得到控件的宽和高
   int width = getWidth();
   int height = getHeight();
   //得到我们的图片以及宽和高
   Drawable d = getDrawable();
   if(d == null)
    return;

   int dw = d.getIntrinsicWidth();
   int dh = d.getIntrinsicHeight();
   float scale = 1.0f;//缩放值
   //如果图片的宽度大于控件高度,但是宽度小于控件的宽度,将其缩小
   if(dw > width && dh < height){
    scale = width*1.0f/dw;
   }
   else if(dh > height && dw < width){
    scale = height*1.0f /dh;
   }

   else if(dw > width && dh > height){
    scale = Math.min(width*1.0f/dw, height*1.0f/dh);
   }
   else if(dw < width && dh < height){
    scale = Math.min(width *1.0f/dw, height*1.0f/dh);
   }
   /*
    * 得到初始化时缩放的比例
    * */

   mInitScale = scale;
   mMaxScale = mInitScale * 4;
   mMidScale = mInitScale * 2;

   //将图片移动到当前控件的中心
   int dx = getWidth()/2 - dw /2;
   int dy = getHeight()/2 - dh/2;

   mScaleMatrix.postTranslate(dx, dy);//平移
   mScaleMatrix.postScale(mInitScale, mInitScale,width/2,height/2);//缩放,后面两个参数是缩放的中心点
   setImageMatrix(mScaleMatrix);

   mOnce = true;

 

 

  }
 }
 /**
  * 获取当前图片的缩放值
  * @return
  */
 public float getScale(){
  float[] values = new float[9];
  mScaleMatrix.getValues(values);
  return values[Matrix.MSCALE_X];

 }
 //缩放的区间,initScale maxScale
 @Override
 public boolean onScale(ScaleGestureDetector detector) {
  // TODO Auto-generated method stub
  float scale = getScale();
  float scaleFactor = detector.getScaleFactor();//得到缩放的值

  if(getDrawable() == null){
   return true;
  }
  //缩放范围的控制
  if((scale < mMaxScale && scaleFactor > 1.0f) || (scale > mInitScale && scaleFactor < 1.0f)){
   if(scale * scaleFactor < mInitScale){
    scaleFactor = mInitScale / scale;//当手指缩放小于最小值时 ,默认显示最小的比例
   }
   if(scale * scaleFactor > mMaxScale){//当手指缩放大于于最大值时 ,默认显示最大的比例
    scale = mMaxScale/scale;
   }
   //缩放,缩放中心是手指触控的地方
   mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(),detector.getFocusY());
   checkBorderAndCenterWhenScale();

   setImageMatrix(mScaleMatrix);
  }
  return true;//设置完成返回true保证事件能够进行
 }

 /**
  * 获得图片放大缩小以后的宽和高以及l r t b
  * @return
  */
 private RectF getMatrixRectF(){
  Matrix matrix = mScaleMatrix;
  RectF recF = new RectF();
  Drawable d = getDrawable();
  if(d != null){
   recF.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
   matrix.mapRect(recF);
  }
  return recF;
 }
 /**
  * 在缩放的时候进行边界控制范围位置控制
  */

 private void checkBorderAndCenterWhenScale() {
  // TODO Auto-generated method stub
  RectF rect = getMatrixRectF();
  float deltaX = 0;
  float deltaY = 0;

  float width = getWidth();
  float height = getHeight();
  //缩放时进行边界检测,放在出现白边
  if(rect.width() >= width){
   if(rect.left > 0){//处理左边的空白
    deltaX = -rect.left;
   }
   if(rect.right < width){//处理右边的空白
    deltaX = (int) (width - rect.right);
   }
  }
  if(rect.height() >= height){
   if(rect.top > 0){
    deltaY = -rect.top;
   }
   if(rect.bottom < height){
    deltaY = height - rect.bottom;
   }
  }
  //如果宽度或高度小于控件的宽或高,则让其居中
  if(rect.width() < width){
   deltaX = width/2f -rect.right + rect.width()/2f;
  }
  if(rect.height() < height){
   deltaY = height /2f -rect.bottom + rect.height()/2f;
  }
  mScaleMatrix.postTranslate(deltaX, deltaY);

 }

 @Override
 public boolean onScaleBegin(ScaleGestureDetector detector) {
  // TODO Auto-generated method stub
  return true;//必须返回true
 }

 @Override
 public void onScaleEnd(ScaleGestureDetector detector) {
  // TODO Auto-generated method stub

 }

 @Override
 public boolean onTouch(View v, MotionEvent event) {
  // TODO Auto-generated method stub
  mScaleGestureDetector.onTouchEvent(event);//把event传递给mscaleGestureDetector处理
  return true;//必须返true
 }
}

下面我们来看一篇关于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 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 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个部分,在我们看来,就是缓慢的移动过去了.

[!--infotagslink--]

相关文章

  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • C#窗体布局方式详解

    这篇文章主要介绍了C#窗体布局方式详解的相关资料,需要的朋友可以参考下...2020-06-25
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Android WebView加载html5页面实例教程

    如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
  • 深入理解Android中View和ViewGroup

    深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
  • Android自定义WebView网络视频播放控件例子

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
  • Android用MemoryFile文件类读写进行性能优化

    java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
  • Android设置TextView竖着显示实例

    TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
  • android.os.BinderProxy cannot be cast to com解决办法

    本文章来给大家介绍关于android.os.BinderProxy cannot be cast to com解决办法,希望此文章对各位有帮助呀。 Android在绑定服务的时候出现java.lang.ClassCastExc...2016-09-20
  • Android 实现钉钉自动打卡功能

    这篇文章主要介绍了Android 实现钉钉自动打卡功能的步骤,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下...2021-03-15
  • Android 开发之布局细节对比:RTL模式

    下面我们来看一篇关于Android 开发之布局细节对比:RTL模式 ,希望这篇文章对各位同学会带来帮助,具体的细节如下介绍。 前言 讲真,好久没写博客了,2016都过了一半了,赶紧...2016-10-02
  • Android中使用SDcard进行文件的读取方法

    首先如果要在程序中使用sdcard进行存储,我们必须要在AndroidManifset.xml文件进行下面的权限设置: 在AndroidManifest.xml中加入访问SDCard的权限如下: <!--...2016-09-20
  • Android开发之PhoneGap打包及错误解决办法

    下面来给各位简单的介绍一下关于Android开发之PhoneGap打包及错误解决办法,希望碰到此类问题的同学可进入参考一下哦。 在我安装、配置好PhoneGap项目的所有依赖...2016-09-20
  • 用Intel HAXM给Android模拟器Emulator加速

    Android 模拟器 Emulator 速度真心不给力,, 现在我们来介绍使用 Intel HAXM 技术为 Android 模拟器加速,使模拟器运行度与真机比肩。 周末试玩了一下在Eclipse中使...2016-09-20
  • Android判断当前屏幕是全屏还是非全屏

    在安卓开发时我碰到一个问题就是需要实现全屏,但又需要我们来判断出用户是使用了全屏或非全屏了,下面我分别找了两段代码,大家可参考。 先来看一个android屏幕全屏实...2016-09-20
  • Android开发中布局中的onClick简单完成多控件时的监听的利与弊

    本文章来为各位介绍一篇关于Android开发中布局中的onClick简单完成多控件时的监听的利与弊的例子,希望这个例子能够帮助到各位朋友. 首先在一个控件加上这么一句:and...2016-09-20
  • js中flexible.js实现淘宝弹性布局方案

    这篇文章主要介绍了js中flexible.js实现淘宝弹性布局方案,需要的朋友可以参考下...2015-12-25