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

 更新时间:2016年10月2日 16:23  点击:4538
下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false">
 
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="60dp"
        android:background="@mipmap/www" />
 
 
    <LinearLayout
        android:id="@+id/ll_bottom"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_alignParentBottom="true"
        android:background="#F8549D"
        android:elevation="10dp"
        android:orientation="horizontal">
 
        <RelativeLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_gravity="bottom"
            android:layout_weight="1">
 
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:src="@mipmap/ic_launcher" />
 
        </RelativeLayout>
 
        <RelativeLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_gravity="bottom"
            android:layout_weight="1">
 
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:src="@mipmap/ic_launcher" />
 
        </RelativeLayout>
 
        <RelativeLayout
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_gravity="bottom"
            android:background="@drawable/bottom_bg_shape">
 
            <ImageView
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_centerInParent="true"
                android:src="@mipmap/icon_go" />
 
        </RelativeLayout>
 
 
        <RelativeLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_gravity="bottom"
            android:layout_weight="1">
 
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:src="@mipmap/ic_launcher" />
 
        </RelativeLayout>
 
        <RelativeLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_gravity="bottom"
            android:layout_weight="1">
 
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:src="@mipmap/ic_launcher" />
 
        </RelativeLayout>
    </LinearLayout>
 
</RelativeLayout>

这张图呢是我刚刚做的,效果是参考了酷狗音乐播放器来完成的,我想说明重点在底部的导航栏:如图

 再来一张反效果预览图:
 
 
2016-08-04_101104
 
 如何使得蓝色圆形的播放键的显示越过粉色的范围。当然经过合理的布局是可以达到这样的效果的,但是却有更简单的方法。也就是在根布局的属性中加入一个来自Android自身提供的属性:


android:clipChildren="false"

属性解释和说明:

1、android:clipChildren的意思:是否限制子View在其范围内

2、可以通过android:layout_gravity控制超出的部分如何显示。

3、只需在根节点设置android:clipChildren为false即可,默认为true

下面我们来看一篇android Html.fromHtml使用方法,希望文章能够让各位深入的理解到android Html.fromHtml使用细节哦。

项目中往往需要显示一段文本,如果对文本需要特定的效果,就要写自定义的span,这样的工作量会变得很大,目前android支持html格式的文本。提供了两个接口,下面我们就来看一下怎么使用。

1. Spanned android.text.Html.fromHtml(String source) //输入的参数为(html格式的文本)

目前android不支持全部的html的标签,目前只支持与文本显示和段落等标签,对于图片和其他的多媒体,还有一些自定义标签不能识别;

例子:


TextView t3 = (TextView) findViewById(R.id.text3);  
t3.setText(Html.fromHtml( "<b>text3:</b>  Text with a " + "<a href=\"http://www.google.com\">link</a> " +"created in the Java source code using HTML.")); 

另外也可以在string.xml中使用,但是要用用<!–cdata–>去转义。如下例子:


<string name="htmlFormattedText">
 <![CDATA[
 Text with markup for [b]bold[/b]
 and [i]italic[/i] text.
 
 There is also support for a
 <tt>teletype-style</tt> font.
 But no use for the <code>code</code>
 tag!
 
 ]]></string>
 
TextView view = (TextView)findViewById(R.id.sampleText);
String formattedText = getString(R.string.htmlFormattedText);
Spanned result = Html.fromHtml(formattedText);
view.setText(result);
 

2.Spanned android.text.Html.fromHtml(String source, ImageGetter imageGetter, TagHandler tagHandler)

Source:    需处理的html文本
imageGetter :对图片处理(处理html中的图片标签)

tagHandler  :对标签进行处理(相当于自定义的标签处理,在这里面可以处理自定义的标签)

具体不细说,大家感兴趣可以自行学习;在实际项目里我们就可以使用第一种方式来替代之前我们用的自定义span了。

下面小编为各位整理一些关于安卓开发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 开发之布局细节对比: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网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。

因为业务需要,以下代码均以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()方法的第二个参数,使用一个全局变量把它引用起来

[!--infotagslink--]

相关文章

  • php 中file_get_contents超时问题的解决方法

    file_get_contents超时我知道最多的原因就是你机器访问远程机器过慢,导致php脚本超时了,但也有其它很多原因,下面我来总结file_get_contents超时问题的解决方法总结。...2016-11-25
  • HTTP 408错误是什么 HTTP 408错误解决方法

    相信很多站长都遇到过这样一个问题,访问页面时出现408错误,下面一聚教程网将为大家介绍408错误出现的原因以及408错误的解决办法。 HTTP 408错误出现原因: HTT...2017-01-22
  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • php抓取网站图片并保存的实现方法

    php如何实现抓取网页图片,相较于手动的粘贴复制,使用小程序要方便快捷多了,喜欢编程的人总会喜欢制作一些简单有用的小软件,最近就参考了网上一个php抓取图片代码,封装了一个php远程抓取图片的类,测试了一下,效果还不错分享...2015-10-30
  • ps把文字背景变透明的操作方法

    ps软件是现在非常受大家喜欢的一款软件,有着非常不错的使用功能。这次文章就给大家介绍下ps把文字背景变透明的操作方法,喜欢的一起来看看。 1、使用Photoshop软件...2017-07-06
  • intellij idea快速查看当前类中的所有方法(推荐)

    这篇文章主要介绍了intellij idea快速查看当前类中的所有方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-09-02
  • Mysql select语句设置默认值的方法

    1.在没有设置默认值的情况下: 复制代码 代码如下:SELECT userinfo.id, user_name, role, adm_regionid, region_name , create_timeFROM userinfoLEFT JOIN region ON userinfo.adm_regionid = region.id 结果:...2014-05-31
  • js导出table数据到excel即导出为EXCEL文档的方法

    复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta ht...2013-10-13
  • js基础知识(公有方法、私有方法、特权方法)

    本文涉及的主题虽然很基础,在许多人看来属于小伎俩,但在JavaScript基础知识中属于一个综合性的话题。这里会涉及到对象属性的封装、原型、构造函数、闭包以及立即执行表达式等知识。公有方法 公有方法就是能被外部访问...2015-11-08
  • mysql 批量更新与批量更新多条记录的不同值实现方法

    批量更新mysql更新语句很简单,更新一条数据的某个字段,一般这样写:复制代码 代码如下:UPDATE mytable SET myfield = 'value' WHERE other_field = 'other_value';如果更新同一字段为同一个值,mysql也很简单,修改下where即...2013-10-04
  • ps怎么制作倒影 ps设计倒影的方法

    ps软件是一款非常不错的图片处理软件,有着非常不错的使用效果。这次文章要给大家介绍的是ps怎么制作倒影,一起来看看设计倒影的方法。 用ps怎么做倒影最终效果&#819...2017-07-06
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • c#中分割字符串的几种方法

    单个字符分割 string s="abcdeabcdeabcde"; string[] sArray=s.Split('c'); foreach(string i in sArray) Console.WriteLine(i.ToString()); 输出下面的结果: ab de...2020-06-25
  • PHP 验证码不显示只有一个小红叉的解决方法

    最近想自学PHP ,做了个验证码,但不知道怎么搞的,总出现一个如下图的小红叉,但验证码就是显示不出来,原因如下 未修改之前,出现如下错误; (1)修改步骤如下,原因如下,原因是apache权限没开, (2)点击打开php.int., 搜索extension=ph...2013-10-04
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • 安卓手机wifi打不开修复教程,安卓手机wifi打不开解决方法

    手机wifi打不开?让小编来告诉你如何解决。还不知道的朋友快来看看。 手机wifi是现在生活中最常用的手机功能,但是遇到手机wifi打不开的情况该怎么办呢?如果手机wifi...2016-12-21
  • 连接MySql速度慢的解决方法(skip-name-resolve)

    最近在Linux服务器上安装MySql5后,本地使用客户端连MySql速度超慢,本地程序连接也超慢。 解决方法:在配置文件my.cnf的[mysqld]下加入skip-name-resolve。原因是默认安装的MySql开启了DNS的反向解析。如果禁用的话就不能...2015-10-21
  • js控制页面控件隐藏显示的两种方法介绍

    javascript控制页面控件隐藏显示的两种方法,方法的不同之处在于控件隐藏后是否还在页面上占位 方法一: 复制代码 代码如下: document.all["panelsms"].style.visibility="hidden"; document.all["panelsms"].style.visi...2013-10-13
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • C#方法的总结详解

    本篇文章是对C#方法进行了详细的总结与介绍,需要的朋友参考下...2020-06-25