一、SurfaceView和VIew的区别
1、VIew主要适用于主动更新情况,并且只能在主线程绘制和更新画面,以及在绘图时没有使用双缓冲机制
2、surfaceView主要适用于被动更新,如频繁的刷新,因为它可以通过子线程来进行页面的刷新,而且在底层已经实现双缓冲机制,绘图时不会出现闪烁问题
总而言之,SurfaceView继承自View,主要用于视频、音频或耗时的绘图和经常更新视图(地图等等)显示,如果自定义的view需要频繁刷新,或者刷新时处理的数据量大,则可以考虑surfaceview取代view,因为View在主线程中会阻塞主线程,若view的刷新操作超过16ms则用户视觉会感到卡顿,由于surfaceView是子线程处理就不会
以下一段结论是抄自老罗之旅的 :http://blog.csdn.net/luoshengyang/article/details/8661317/
SurfaceView类的成员变量mRequestedType描述的是SurfaceView的绘图表面的类型,一般来说,它的值可能等于SURFACE_TYPE_NORMAL,也可能等于SURFACE_TYPE_PUSH_BUFFERS。
当一个SurfaceView的绘图表面的类型等于SURFACE_TYPE_NORMAL的时候,就表示该SurfaceView的绘图表面所使用的内存是一块普通的内存。一般来说,这块内存是由SurfaceFlinger服务来分配的,我们可以在应用程序内部自由地访问它,即可以在它上面填充任意的UI数据,然后交给SurfaceFlinger服务来合成,并且显示在屏幕上。在这种情况下,SurfaceFlinger服务使用一个Layer对象来描述该SurfaceView的绘图表面。
当一个SurfaceView的绘图表面的类型等于SURFACE_TYPE_PUSH_BUFFERS的时候,就表示该SurfaceView的绘图表面所使用的内存不是由SurfaceFlinger服务分配的,因而我们不能够在应用程序内部对它进行操作。例如,当一个SurfaceView是用来显示摄像头预览或者视频播放的时候,我们就会将它的绘图表面的类型设置为SURFACE_TYPE_PUSH_BUFFERS,这样摄像头服务或者视频播放服务就会为该SurfaceView绘图表面创建一块内存,并且将采集的预览图像数据或者视频帧数据源源不断地填充到该内存中去。注意,这块内存有可能是来自专用的硬件的,例如,它可能是来自视频卡的。在这种情况下,SurfaceFlinger服务使用一个LayerBuffer对象来描述该SurfaceView的绘图表面。
从上面的描述就得到一个重要的结论:绘图表面类型为SURFACE_TYPE_PUSH_BUFFERS的SurfaceView的UI是不能由应用程序来控制的,而是由专门的服务来控制的,例如,摄像头服务或者视频播放服务,同时,SurfaceFlinger服务会使用一种特殊的LayerBuffer来描述这种绘图表面。使用LayerBuffer来描述的绘图表面在进行渲染的时候,可以使用硬件加速,例如,使用copybit或者overlay来加快渲染速度,从而可以获得更流畅的摄像头预览或者视频播放。
注意,我们在创建了一个SurfaceView之后,可以调用它的成员函数getHolder获得一个SurfaceHolder对象,然后再调用该SurfaceHolder对象的成员函数setType来修改该SurfaceView的绘图表面的类型,即修改该SurfaceView的成员变量mRequestedType的值
这段结论就说明决定surfaceView的内存是普通内存(由开发者自己决定用来绘制什么)还是专用的内存(开发者无法使用这块内存)由mRequestType决定,而我们可以通过holde.setType()设置,用于决定是显示视频还是自定义的surfaceView。
以下直接用demo讲解自定义的view和专用于视频的surfaceView
1、自定义surfaceView,用于绘制sin曲线
package com.example.administrator.surfaceviewdemo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* author : zhongwr on 2016/7/12
* SurfaceView类的成员函数draw和dispatchDraw的参数canvas所描述的都是建立在宿主窗口的绘图表面上的画布,
* 因此,在这块画布上绘制的任何UI都是出现在宿主窗口的绘图表面上的。
*
* 本来SurfaceView类的成员函数draw是用来将自己的UI绘制在宿主窗口的绘图表面上的,
* 但是这里我们可以看到,如果当前正在处理的SurfaceView不是用作宿主窗口面板的时候,
* 即其成员变量mWindowType的值不等于WindowManager.LayoutParams.TYPE_APPLICATION_PANEL的时候,
* SurfaceView类的成员函数draw只是简单地将它所占据的区域绘制为黑色。
*/
public class SinSurfaceView extends SurfaceView implements Runnable {
private SurfaceHolder mSurfaceHolder;
/**
* 用于保存正弦路径坐标
*/
private Path mPath;
private Paint mPaint;
public SinSurfaceView(Context context) {
super(context);
init();
}
public SinSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SinSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPaint.setColor(Color.GREEN);
//连接处更加平滑
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPath = new Path();
/**通过holder去申请绘图表面的画布,surfaceview其实draw()或dispathDraw()都只是一块默认的黑色区域,并不是用作宿主
* 真正要做的事情由开发者自行绘制,绘制之前就是通过holder获取一块内存区域的画布,
* 然后可在UI线程或工作线程在这个画布上进行绘制所需要的视图,最后还是通过holder提交这个画布就可以显示
* */
mSurfaceHolder = getHolder();
//回调
mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
/***
* surfaceview的绘图表面(就是activity宿主创建一个透明的表面用于surfaceView绘制)被创建时执行
* 在updateWindow()创建宿主(activity的窗口)的绘图表面时会回调,虽然surfaceView是独立于一个线程但还是离不开宿主窗口,
* 最后还是要粘贴到window中
*
* surfaceCreated方法,是当SurfaceView被显示时会调用的方法,所以你需要再这边开启绘制的线 程
*
* @param holder
*/
@Override
public void surfaceCreated(SurfaceHolder holder) {
new Thread(SinSurfaceView.this).start();
}
/**
* 创建、更新会认为发生变化也会回调这个方法
* @param holder
* @param format
* @param width
* @param height
*/
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
/***
*surfaceDestroyed方法是当SurfaceView被隐藏会销毁时调用的方法,在这里你可以关闭绘制的线程
* @param holder
*/
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isDrawing = false;
}
});
}
/***
* 是否在绘制:用于关闭子线程:true则表示一直循环
*/
private boolean isDrawing = true;
private int drawX;
private int drawY;
/***
* 注意这个是在子线程中绘制的
*/
@Override
public void run() {
while (isDrawing) {
drawX++;
drawY = (int) (100 * Math.sin(drawX * 2 * Math.PI / 180) + 400);
mPath.lineTo(drawX, drawY);
draw(mPath);
}
}
/***
* 注意这个是在子线程中绘制的,surface支持子线程更新ui,所以
*/
private void draw(Path path) {
Canvas canvas = null;
//给画布加锁,防止线程安全,防止该内存区域被其他线程公用
canvas = mSurfaceHolder.lockCanvas();
if (null != canvas) {
//清屏操作或者设置背景
canvas.drawColor(Color.WHITE);
canvas.drawPath(mPath, mPaint);
//提交显示视图并解锁,防止长期占用此内存
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
2、surfaceView用于显视频的demo
一、注意两点:
(1)mediaPlayer = new MediaPlayer()必须只初始化一次,若是放在play()方法中初始化,由于可能会多次执行直接或间接play()这个方法,导致多次初始化,以至于在onPrepared(MediaPlayer mp)的回调中(假设监听器用的是同一个,看demo),mediaPlayer和mp不是同一个,因为多次初始化原因,导致不是同一个对象,保证是一个对象就行
(2)播放本地视频音频时,明明就有文件,但是通过/sdcard/xxxx.mp4或者storage/sdcard0/xxx.mp4都说找不到这个路径,然后查看异常日志会发现如下日志
<1>、 QCMediaPlayer mediaplayer NOT present
发现这个不要惊慌,虽然我也搞了好长时间。最后才发现,其实就是没加sd卡的权限,只要在manifest中加入权限
使用高通的手机平台都有这个问题,MTK 平台的就没有。主要是高通平台不支持直接 new Mediaplayer(); 必须用 MediaPlayer.create(xxx)方法 好像并没有什么关系
<2>、Should have subtitle controller already set
发现这个异常也不要惊慌,毕竟这个好像是sdk4.4之后源码才加入的,可以不用管,不会影响视频的播放
二、谈谈mediaPlayer的创建
要是仔细的话你会发现,系统有好几个MediaPlayer.create(xxx),但是我们几乎都是自己new MediaPlayer()创建,这两个有什么区别呢,接下来说说
要是看过 MediaPlayer.create(xxx)这个方法的源码,你会发现,最后它也是通过new MediaPlayer(),只不过MediaPlayer.create(xxx)是已经初始化了的,然后值得注意的是mediaPlayer.prepare()和mediaPlayer.setDatesource(context,Uri);
mediaPlayer.prepare()主要是同步预加载,若是预加载资源则mediaPlayer.seekTo()是不会生效的,同样mediaPlayer.getCurrentPosition()也不会生效,因为这都是mediaPlayer.prepareSync()这个异步加载才会生效的,即他们都是异步加载的方法 。
mediaPlayer.setDatesource(context,Uri); 这个方法加载资源时不需要权限的,直接可以播放,所以网络上出现问题(2)的<1>时,网上都说用create()方法创建mediaPlayer的就没问题
以下摘抄子网络,各个方法详解:
1.如何获得MediaPlayer实例:
可以使用直接new的方式:
MediaPlayer mp = new MediaPlayer();
也可以使用create的方式,如:
MediaPlayer mp = MediaPlayer.create(this, R.raw.test);//这时就不用调用setDataSource了
2.如何设置要播放的文件:
MediaPlayer要播放的文件主要包括3个来源:
a. 用户在应用中事先自带的resource资源
例如:MediaPlayer.create(this, R.raw.test);
b. 存储在SD卡或其他文件路径下的媒体文件
例如:mp.setDataSource("/sdcard/test.mp3");
c. 网络上的媒体文件
例如:mp.setDataSource("mp3或者mp4的地址");
3.MediaPlayer常用API
MediaPlayer的setDataSource一共四个方法:
setDataSource (String path)
setDataSource (FileDescriptor fd)
setDataSource (Context context, Uri uri)
setDataSource (FileDescriptor fd, long offset, long length)
对播放器的主要控制方法:
Android通过控制播放器的状态的方式来控制媒体文件的播放,其中:
1.prepare()和prepareAsync() 提供了同步和异步两种方式设置播放器进入prepare状态,需要注意的是,如果MediaPlayer实例是由create方法创建的,那么第一次启动播放前不需要再调用prepare()了,因为create方法里已经调用过了。
2. start()是真正启动文件播放的方法
3.pause()和stop()比较简单,起到暂停和停止播放的作用
4.seekTo()是定位方法,可以让播放器从指定的位置开始播放,需要注意的是该方法是个异步方法,也就是说该方法返回时并不意味着定位完成,尤其是播放的网络文件,真正定位完成时会触发OnSeekComplete.onSeekComplete(),如果需要是可以调用setOnSeekCompleteListener(OnSeekCompleteListener)设置监听器来处理的。
5.release()可以释放播放器占用的资源,一旦确定不再使用播放器时应当尽早调用它释放资源。
6.reset()可以使播放器从Error状态中恢复过来,重新会到Idle状态。
废话不说了:直接上代码:其它代码注释很明了,直接看应该就能看懂,功能自己扩展
package com.example.administrator.surfaceviewdemo;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.TimedText;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.lang.ref.WeakReference;
/**
* 视频播放类
* @author zhognwr
*/
public class PlayVedioActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "PlayVedioActivity";
private SeekBar videoSeekBar;
private TextView tvPlayingTime;
private TextView tvVideoTotalTime;
private ImageView ivPlay;
private ImageView ivPause;
private MediaPlayer mediaPlayer;
private SurfaceView surfaceView;
private EditText etPath;
/**
* 当前播放视频位置
*/
private int currentPosition;
public static void startInstance(Context context) {
context.startActivity(new Intent(context, PlayVedioActivity.class));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.video_player_layout);
initView();
initListener();
initData();
}
@Override
protected void onPause() {
super.onPause();
updateSeekBarHandler.removeMessages(0);
mediaPlayer.reset();
mediaPlayer.release();
}
private void initData() {
mediaPlayer= new MediaPlayer();
// 把输送给surfaceView的视频画面,直接显示到屏幕上,不要维持它自身的缓冲区
surfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
// surfaceView.getHolder().setFixedSize(176, 144);
surfaceView.getHolder().setKeepScreenOn(true);
surfaceView.getHolder().addCallback(new SurfaceCallback());
}
private void initListener() {
ivPause.setOnClickListener(this);
ivPlay.setOnClickListener(this);
videoSeekBar.setOnSeekBarChangeListener(new SeekBarListener());
surfaceView.getHolder().addCallback(new SurfaceCallback());
}
private void initView() {
surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
videoSeekBar = (SeekBar) findViewById(R.id.videoProgress);
tvPlayingTime = (TextView) findViewById(R.id.textHasPalyTime);
tvVideoTotalTime = (TextView) findViewById(R.id.video_total_time);
ivPlay = (ImageView) findViewById(R.id.imagePlay);
ivPause = (ImageView) findViewById(R.id.iv_pause);
etPath = (EditText) findViewById(R.id.et_path);
}
@Override
public void onClick(View v) {
if (v == ivPlay) {//播放
if (!mediaPlayer.isPlaying()) {//播放、继续播放
// pause = false;
mediaPlayer.start();
}
} else if (v == ivPause) {//暂停
// pause = true;
mediaPlayer.pause();
}
}
/**
* 重新开始播放
*/
protected void replay() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.seekTo(0);
Toast.makeText(this, "重新播放", Toast.LENGTH_SHORT).show();
return;
}
}
/**
* 暂停或继续
*/
protected void pause() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
Toast.makeText(this, "暂停播放", Toast.LENGTH_LONG).show();
}
}
/**
* SeekBar监听类,监听用户对seekbar滑动的位置,已达到控制视频播放进度
*/
private class SeekBarListener implements SeekBar.OnSeekBarChangeListener {
/***
* 一直滑动不断变化的seekBar时,触发
*
* @param seekBar
* @param progress
* @param fromUser
*/
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
Log.d(TAG, "Changed progress " + progress);
Log.d(TAG, "Changed getProgress() " + seekBar.getProgress());
tvPlayingTime.setText(getTime(seekBar.getProgress() / 1000));
}
/**
* 用户开始滑动seekBar时触发
*
* @param seekBar
*/
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
Log.d(TAG, "start getProgress() " + seekBar.getProgress());
}
/**
* 当用户结束对滑块的滑动时,将mediaPlayer播放位置设为滑块结束对应位置
*
* @param seekBar
*/
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
Log.d(TAG, "Stop getProgress() " + seekBar.getProgress());
currentPosition = seekBar.getProgress();
mediaPlayer.seekTo(currentPosition);
tvPlayingTime.setText(getTime(seekBar.getProgress() / 1000));
}
}
/*
* 当SurfaceView所在的Activity离开了前台,SurfaceView会被destroy,
* 当Activity又回到了前台时,SurfaceView会被重新创建,并且是在OnResume()方法之后被创建
*/
private class SurfaceCallback implements SurfaceHolder.Callback {
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
/**
* 创建SurfaceView时开始从上次位置播放或重新播放
*
* @param holder
*/
@SuppressLint("NewApi")
public void surfaceCreated(SurfaceHolder holder) {
// audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
// maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);// 取得最大音量
// curVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);// 获取当前音量
// 创建SurfaceHolder的时候,如果存在上次播放的位置,则按照上次播放位置进行播放
play("/sdcard/basketball.mp4");
}
/**
* 离开SurfaceView时停止播放,保存播放位置
*/
public void surfaceDestroyed(SurfaceHolder holder) {
// 隐藏view的时候销毁SurfaceHolder的时候记录当前的播放位置并停止播放
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
currentPosition = mediaPlayer.getCurrentPosition();
mediaPlayer.stop();
updateSeekBarHandler.removeMessages(0);
}
}
}
/**
* 开始播放
*
* @param playUrl 播放视频的地址
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
protected void play(String playUrl) {
// 获取视频文件地址:放在sd卡根目录下的视频地址,这个地址也支持网络地址:比如http://...
// String path = "storage/sdcard0/test.mp4";
// 获取视频文件地址
String path = etPath.getText().toString().trim();
// String path = "/sdcard/basketball.mp4";
File file = new File(path);
if (!file.exists()) {
Toast.makeText(this, "视频文件路径错误或忘了加sd卡权限了", Toast.LENGTH_LONG).show();
return;
}
path = file.getAbsolutePath();
Log.d(TAG, "path = " + path);
try {
//mediaPlayer= new MediaPlayer(); 这个初始化不能放在这里,因为surfaceView可能会多次执行,导致mediaPlayer重新被初始化,由于监听器用的是同一个导致回调的时候会出现不适同一个mediaplayer的情况
//此方法创建同步加载,直接start
// mediaPlayer = MediaPlayer.create(this, Uri.parse(path), null);
mediaPlayer.reset();
// 设置播放的视频源
mediaPlayer.setDataSource(path);
// 设置显示视频的SurfaceHolder
mediaPlayer.setDisplay(surfaceView.getHolder());
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setOnPreparedListener(mediaPlayerListener);
mediaPlayer.setOnCompletionListener(mediaPlayerListener);
mediaPlayer.setOnInfoListener(mediaPlayerListener);
mediaPlayer.setOnErrorListener(mediaPlayerListener);
mediaPlayer.setOnBufferingUpdateListener(mediaPlayerListener);
mediaPlayer.setOnSeekCompleteListener(mediaPlayerListener);
mediaPlayer.prepareAsync();// 缓冲,装载
// mediaPlayer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 内部消息队列类,主要获取updateThread发来的CurrentPosition和MaxPosition设置给SeekBar
*/
private Handler updateSeekBarHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "handleMessage curPosition = " + mediaPlayer.getCurrentPosition());
videoSeekBar.setProgress(mediaPlayer.getCurrentPosition());
tvPlayingTime.setText(getTime(mediaPlayer.getCurrentPosition() / 1000));
sendEmptyMessageDelayed(0, 1000);
}
};
private MediaPlayerListener mediaPlayerListener = new MediaPlayerListener();
private class MediaPlayerListener implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnInfoListener
, MediaPlayer.OnErrorListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnSeekCompleteListener {
/**
* 视频装载完成,可以播放
*
* @param mp
*/
public void onPrepared(MediaPlayer mp) {
Log.d(TAG, "装载完成");
Log.d(TAG, "装载完成 media same ? "+(mp==mediaPlayer));
mediaPlayer.seekTo(currentPosition);
mp.start();
// 按照初始位置播放
// 设置进度条的最大进度为视频流的最大播放时长
videoSeekBar.setMax(mediaPlayer.getDuration());
tvVideoTotalTime.setText(getTime(mediaPlayer.getDuration() / 1000));
//更新seekBar进度
updateSeekBarHandler.sendEmptyMessageDelayed(0, 1000);
// 开始线程,更新进度条的刻度
int current = mediaPlayer.getCurrentPosition();
videoSeekBar.setProgress(current);
}
/**
* 在播放结束被回调
*
* @param mp
*/
@Override
public void onCompletion(MediaPlayer mp) {
Log.d(TAG, "onCompletion " + mp.getCurrentPosition());
mediaPlayer.seekTo(currentPosition);
}
/**
* 播放视频,处理视频过程的视频信息
*
* @param mp
* @param what
* @param extra
* @return
*/
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
Log.d(TAG, "---------onInfo------what---" + what + "--extra--" + extra);
if (MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START == what) {
//缓冲视频开始
} else if (MediaPlayer.MEDIA_INFO_BUFFERING_START == what) {
//网络连接异常
} else if (MediaPlayer.MEDIA_INFO_BUFFERING_END == what) {
//缓冲完成
} else if (MediaPlayer.MEDIA_INFO_METADATA_UPDATE == what) {
Log.d(TAG, "---------MEDIA_INFO_METADATA_UPDATE---------");
}
return false;
}
/***
* 播放出错
*
* @param mp
* @param what
* @param extra
* @return
*/
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// 发生错误重新播放
// play("");
Log.d(TAG, "onError");
if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED ||
what == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {// 网络连接异常
return true;
}
return false;
}
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
Log.d(TAG, "percent = " + percent);
}
/***
*
* seekTo()是定位方法,可以让播放器从指定的位置开始播放,需要注意的是该方法是个异步方法,
* 也就是说该方法返回时并不意味着定位完成,尤其是播放的网络文件,
* 真正定位完成时会触发OnSeekComplete.onSeekComplete(),
* 如果需要是可以调用setOnSeekCompleteListener(OnSeekCompleteListener)设置监听器来处理的。
* @param mp
*/
@Override
public void onSeekComplete(MediaPlayer mp) {
Log.d(TAG, "---------onSeekComplete---------");
mp.start();
}
}
/**
* 秒转换成00:00
*
* @param ms
* @return
*/
private String getTime(int ms) {
try {
return appendStrs(ms / 60 + "") + ":" + appendStrs(ms % 60 + "");
} catch (Exception e) {
}
return "00:00";
}
/**
* 字符串前面补加0
*
* @param str
* @return
*/
private String appendStrs(String str) {
String appendStr = "00";
if (null == str || str.trim().isEmpty()) {
return appendStr;
}
if (str.length() == appendStr.length()) {
return str;
}
return appendStr.substring(0, appendStr.length() - str.length()) + str;
}
}
视频Demo:http://download.csdn.net/detail/zhongwn/9576021