Android MediaPlayer与Http Proxy结合之基础篇
最后更新于:2022-04-01 15:47:47
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/),引用必须注明出处!
最近半年都忙着Android TV项目,在春节假期才有时间写点东西。先在这里给大家拜个年,祝大家龙年快乐...
直接进入主题:本文将会教大家如何实现一个简单的代理服务器(仅支持Http Get),与Android的MediaPlayer结合,从而可以扩展出“播放 防盗链的媒体文件”,“边播放边保存”等的功能。
本文的代码可以到这里下载:[http://download.csdn.net/detail/hellogv/4047134](http://download.csdn.net/detail/hellogv/4047134),代码分为两个工程:
1. J2SE工程:HttpGetProxy,在PC上实现简单的代理服务器,核心类是HttpGetProxy.java,非常容易使用,这里就不唠叨了直接贴出运行效果图:![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a89c746.gif)
1. Android工程:本文重点,必须唠叨一下。MediaPlayer播放网络音频([http://blog.csdn.net/hellogv/article/details/6406732](http://blog.csdn.net/hellogv/article/details/6406732))与HttpGetProxy.java结合,通过代理服务器播放网络音频。
接下来贴出HttpGetProxy.java的原理图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a8b4c66.gif)
接下来贴出HttpGetProxy.java的源码:
通过RemoteSocket的out_remoteSocket可以访问防盗链资源,HttpGetProxy通过2个线程来实现转发,可以在两个线程内实现保存的功能。
~~~
package com.musicplayer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import android.util.Log;
public class HttpGetProxy {
final private String LOCAL_IP_ADDRESS = "127.0.0.1";
final private int HTTP_PORT = 80;
private ServerSocket localServer = null;
private Socket localSocket = null;
private Socket remoteSocket = null;
private String remoteIPAddress;
private InputStream in_remoteSocket;
private OutputStream out_remoteSocket;
private InputStream in_localSocket;
private OutputStream out_localSocket;
private interface OnFinishListener {
void onFinishListener();
}
public HttpGetProxy(int localport) {
// --------建立代理中转服务器-----------//
try {
localServer = new ServerSocket(localport, 1,
InetAddress.getByName(LOCAL_IP_ADDRESS));
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 结束时,清除所有资源
*/
private OnFinishListener finishListener =new OnFinishListener(){
@Override
public void onFinishListener() {
System.out.println("..........release all..........");
Log.e("---->","..........release all..........");
try {
in_localSocket.close();
out_remoteSocket.close();
in_remoteSocket.close();
out_localSocket.close();
localSocket.close();
remoteSocket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
public void startProxy(String remoteIpAddr) throws IOException {
remoteIPAddress = remoteIpAddr;
SocketAddress address = new InetSocketAddress(remoteIPAddress,HTTP_PORT);
// --------连接目标服务器---------//
remoteSocket = new Socket();
remoteSocket.connect(address);
System.out.println("..........remote Server connected..........");
Log.e("---->","..........remote Server connected..........");
in_remoteSocket = remoteSocket.getInputStream();
out_remoteSocket = remoteSocket.getOutputStream();
System.out.println("..........init remote Server I/O..........");
/**
* 接收本地request,并转发到远程服务器
*/
new Thread() {
public void run() {
int bytes_read;
byte[] local_request = new byte[5120];
try {
// 本地Socket
localSocket = localServer.accept();
System.out.println("..........localSocket connected..........");
Log.e("---->","..........localSocket connected..........");
in_localSocket = localSocket.getInputStream();
out_localSocket = localSocket.getOutputStream();
System.out.println("..........init local Socket I/O..........");
Log.e("---->","..........init local Socket I/O..........");
String buffer = "";
while ((bytes_read = in_localSocket.read(local_request)) != -1) {
String str = new String(local_request);
System.out.println("localSocket " + str);
Log.e("localSocket---->",str);
buffer = buffer + str;
if (buffer.contains("GET")
&& buffer.contains("\r\n\r\n")) {
//---把request中的本地ip改为远程ip---//
buffer = buffer.replace(LOCAL_IP_ADDRESS,remoteIPAddress);
System.out.println("已经替换IP");
out_remoteSocket.write(buffer.getBytes());
out_remoteSocket.flush();
continue;
} else{
out_remoteSocket.write(buffer.getBytes());
out_remoteSocket.flush();
}
}
System.out.println("..........local finish receive...........");
Log.e("---->","..........local finish receive..........");
finishListener.onFinishListener();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
/**
* 接收远程服务器reply,并转发到本地客户端
*/
new Thread() {
public void run() {
int bytes_read;
byte[] remote_reply = new byte[5120];
try {
System.out.println("..........remote start to receive...........");
Log.e("---->","..........remote start to receive..........");
while ((bytes_read = in_remoteSocket.read(remote_reply)) != -1) {
//System.out.println("remoteSocket " + remote_reply.length);
//System.out.println("remoteSocket " + new String(remote_reply));
out_localSocket.write(remote_reply, 0, bytes_read);
out_localSocket.flush();
}
System.out.println("..........remote finish receive...........");
Log.e("---->","..........remote finish receive..........");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
}
~~~
可动态布局的Android抽屉之完整篇
最后更新于:2022-04-01 15:47:44
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,欢迎转摘,引用必须注明出处!
上次介绍了[基础篇](http://blog.csdn.net/hellogv/article/details/6789698),讲解了自定义抽屉控件的基础实现,这次就在基础篇的基础上加入拖拉功能。拖拉功能基于GestureDetector,GestureDetector的基本使用方式不是本文介绍的重点,有兴趣的童鞋可以上网查询相关的教程。
本文的抽屉控件相对于基础篇的抽屉控件多了以下功能:
> 1.支持手势拖拉
> 2.拖拉到一半时,可以自动展开或者收缩。
具体如下图:
> ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a84c3f1.gif)
>
>
>
本文的源码可以到这里下载:[http://download.csdn.net/detail/hellogv/3642418](http://download.csdn.net/detail/hellogv/3642418)
只贴出抽屉组件的源码,其他源文件与基础篇的一样:
~~~
public class Panel extends LinearLayout implements GestureDetector.OnGestureListener{
public interface PanelClosedEvent {
void onPanelClosed(View panel);
}
public interface PanelOpenedEvent {
void onPanelOpened(View panel);
}
private final static int HANDLE_WIDTH=30;
private final static int MOVE_WIDTH=20;
private Button btnHandler;
private LinearLayout panelContainer;
private int mRightMargin=0;
private Context mContext;
private GestureDetector mGestureDetector;
private boolean mIsScrolling=false;
private float mScrollX;
private PanelClosedEvent panelClosedEvent=null;
private PanelOpenedEvent panelOpenedEvent=null;
public Panel(Context context,View otherView,int width,int height) {
super(context);
this.mContext=context;
//定义手势识别
mGestureDetector = new GestureDetector(mContext,this);
mGestureDetector.setIsLongpressEnabled(false);
//改变Panel附近组件的属性
LayoutParams otherLP=(LayoutParams) otherView.getLayoutParams();
otherLP.weight=1;
otherView.setLayoutParams(otherLP);
//设置Panel本身的属性
LayoutParams lp=new LayoutParams(width, height);
lp.rightMargin=-lp.width+HANDLE_WIDTH;
mRightMargin=Math.abs(lp.rightMargin);
this.setLayoutParams(lp);
this.setOrientation(LinearLayout.HORIZONTAL);
//设置Handler的属性
btnHandler=new Button(context);
btnHandler.setLayoutParams(new LayoutParams(HANDLE_WIDTH,height));
//btnHandler.setOnClickListener(handlerClickEvent);
btnHandler.setOnTouchListener(handlerTouchEvent);
this.addView(btnHandler);
//设置Container的属性
panelContainer=new LinearLayout(context);
panelContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
this.addView(panelContainer);
}
private View.OnTouchListener handlerTouchEvent=new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_UP && //onScroll时的ACTION_UP
mIsScrolling==true)
{
LayoutParams lp=(LayoutParams) Panel.this.getLayoutParams();
if (lp.rightMargin >= (-mRightMargin/2)) {//往左超过一半
new AsynMove().execute(new Integer[] { MOVE_WIDTH });// 正数展开
}
else if (lp.rightMargin < (-mRightMargin/2)) {//往右拖拉
new AsynMove().execute(new Integer[] { -MOVE_WIDTH });// 负数收缩
}
}
return mGestureDetector.onTouchEvent(event);
}
};
/**
* 定义收缩时的回调函数
* @param event
*/
public void setPanelClosedEvent(PanelClosedEvent event)
{
this.panelClosedEvent=event;
}
/**
* 定义展开时的回调函数
* @param event
*/
public void setPanelOpenedEvent(PanelOpenedEvent event)
{
this.panelOpenedEvent=event;
}
/**
* 把View放在Panel的Container
* @param v
*/
public void fillPanelContainer(View v)
{
panelContainer.addView(v);
}
/**
* 异步移动Panel
* @author hellogv
*/
class AsynMove extends AsyncTask<Integer, Integer, Void> {
@Override
protected Void doInBackground(Integer... params) {
int times;
if (mRightMargin % Math.abs(params[0]) == 0)// 整除
times = mRightMargin / Math.abs(params[0]);
else
// 有余数
times = mRightMargin / Math.abs(params[0]) + 1;
for (int i = 0; i < times; i++) {
publishProgress(params);
try {
Thread.sleep(Math.abs(params[0]));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... params) {
LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();
if (params[0] < 0)
lp.rightMargin = Math.max(lp.rightMargin + params[0],
(-mRightMargin));
else
lp.rightMargin = Math.min(lp.rightMargin + params[0], 0);
if(lp.rightMargin==0 && panelOpenedEvent!=null){//展开之后
panelOpenedEvent.onPanelOpened(Panel.this);//调用OPEN回调函数
}
else if(lp.rightMargin==-(mRightMargin) && panelClosedEvent!=null){//收缩之后
panelClosedEvent.onPanelClosed(Panel.this);//调用CLOSE回调函数
}
Panel.this.setLayoutParams(lp);
}
}
@Override
public boolean onDown(MotionEvent e) {
mScrollX=0;
mIsScrolling=false;
return false;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();
if (lp.rightMargin < 0)// CLOSE的状态
new AsynMove().execute(new Integer[] { MOVE_WIDTH });// 正数展开
else if (lp.rightMargin >= 0)// OPEN的状态
new AsynMove().execute(new Integer[] { -MOVE_WIDTH });// 负数收缩
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
mIsScrolling=true;
mScrollX+=distanceX;
LayoutParams lp=(LayoutParams) Panel.this.getLayoutParams();
if (lp.rightMargin < -1 && mScrollX > 0) {//往左拖拉
lp.rightMargin = Math.min((lp.rightMargin + (int) mScrollX),0);
Panel.this.setLayoutParams(lp);
Log.e("onScroll",lp.rightMargin+"");
}
else if (lp.rightMargin > -(mRightMargin) && mScrollX < 0) {//往右拖拉
lp.rightMargin = Math.max((lp.rightMargin + (int) mScrollX),-mRightMargin);
Panel.this.setLayoutParams(lp);
}
if(lp.rightMargin==0 && panelOpenedEvent!=null){//展开之后
panelOpenedEvent.onPanelOpened(Panel.this);//调用OPEN回调函数
}
else if(lp.rightMargin==-(mRightMargin) && panelClosedEvent!=null){//收缩之后
panelClosedEvent.onPanelClosed(Panel.this);//调用CLOSE回调函数
}
Log.e("onScroll",lp.rightMargin+"");
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {return false;}
@Override
public void onLongPress(MotionEvent e) {}
@Override
public void onShowPress(MotionEvent e) {}
}
~~~
可动态布局的Android抽屉之基础
最后更新于:2022-04-01 15:47:42
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,欢迎转摘,引用必须注明出处!
以前曾经介绍过[《Android提高第十九篇之"多方向"抽屉》](http://blog.csdn.net/hellogv/article/details/6264706),当这个抽屉组件不与周围组件发生压挤的情况下(周围组件布局不变),是比较好使的,但是如果需要对周围组件挤压,则用起来欠缺美观了。
如下图。在对周围压挤的情况下,抽屉是先把周围的组件一次性压挤,再通过动画效果展开/收缩的,这种做法的好处是快速简单,坏处是如果挤压范围过大,则效果生硬。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a7e40ea.gif)
本文实现的自定义抽屉组件,主要针对这种压挤效果做出改良,渐进式压挤周围组件,使得过渡效果更加美观。如下图。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a8102e1.gif)
本文实现的抽屉原理是酱紫:
1.抽屉组件主要在屏幕不可视区域,手柄在屏幕边缘的可视区域。即 抽屉.rightMargin=-XXX + 手柄.width
2.指定一个周围组件为可压挤,即LayoutParams.weight=1;当然用户也可以指定多个View.
3.使用AsyncTask来实现弹出/收缩的动画,弹出:抽屉.rightMargin+=XX,收缩:抽屉.rightMargin-=XX
总结,本文的自定义抽屉虽然对压挤周围组件有过渡效果,但是比较耗资源,读者可以针对不同的情况考虑使用。
**本文的源码可以到**[**http://download.csdn.net/detail/hellogv/3615686**](http://download.csdn.net/detail/hellogv/3615686)** 下载。**
接下来贴出本文全部源代码:
main.xml的源码:
~~~
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:id="@+id/container">
<GridView android:id="@+id/gridview" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:numColumns="auto_fit"
android:verticalSpacing="10dp" android:gravity="center"
android:columnWidth="50dip" android:horizontalSpacing="10dip" />
</LinearLayout>
~~~
GridView的Item.xml的源码:
~~~
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content" android:paddingBottom="4dip"
android:layout_width="fill_parent">
<ImageView android:layout_height="wrap_content" android:id="@+id/ItemImage"
android:layout_width="wrap_content" android:layout_centerHorizontal="true">
</ImageView>
<TextView android:layout_width="wrap_content"
android:layout_below="@+id/ItemImage" android:layout_height="wrap_content"
android:text="TextView01" android:layout_centerHorizontal="true"
android:id="@+id/ItemText">
</TextView>
</RelativeLayout>
~~~
Panel.java是本文核心,抽屉组件的源码,这个抽屉只实现了从右往左的弹出/从左往右的收缩,读者可以根据自己的需要修改源码来改变抽屉动作的方向:
~~~
public class Panel extends LinearLayout{
public interface PanelClosedEvent {
void onPanelClosed(View panel);
}
public interface PanelOpenedEvent {
void onPanelOpened(View panel);
}
/**Handle的宽度,与Panel等高*/
private final static int HANDLE_WIDTH=30;
/**每次自动展开/收缩的范围*/
private final static int MOVE_WIDTH=20;
private Button btnHandle;
private LinearLayout panelContainer;
private int mRightMargin=0;
private Context mContext;
private PanelClosedEvent panelClosedEvent=null;
private PanelOpenedEvent panelOpenedEvent=null;
/**
* otherView自动布局以适应Panel展开/收缩的空间变化
* @author GV
*
*/
public Panel(Context context,View otherView,int width,int height) {
super(context);
this.mContext=context;
//改变Panel附近组件的属性
LayoutParams otherLP=(LayoutParams) otherView.getLayoutParams();
otherLP.weight=1;//支持压挤
otherView.setLayoutParams(otherLP);
//设置Panel本身的属性
LayoutParams lp=new LayoutParams(width, height);
lp.rightMargin=-lp.width+HANDLE_WIDTH;//Panel的Container在屏幕不可视区域,Handle在可视区域
mRightMargin=Math.abs(lp.rightMargin);
this.setLayoutParams(lp);
this.setOrientation(LinearLayout.HORIZONTAL);
//设置Handle的属性
btnHandle=new Button(context);
btnHandle.setLayoutParams(new LayoutParams(HANDLE_WIDTH,height));
btnHandle.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View arg0) {
LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();
if (lp.rightMargin < 0)// CLOSE的状态
new AsynMove().execute(new Integer[] { MOVE_WIDTH });// 正数展开
else if (lp.rightMargin >= 0)// OPEN的状态
new AsynMove().execute(new Integer[] { -MOVE_WIDTH });// 负数收缩
}
});
//btnHandle.setOnTouchListener(HandleTouchEvent);
this.addView(btnHandle);
//设置Container的属性
panelContainer=new LinearLayout(context);
panelContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
this.addView(panelContainer);
}
/**
* 定义收缩时的回调函数
* @param event
*/
public void setPanelClosedEvent(PanelClosedEvent event)
{
this.panelClosedEvent=event;
}
/**
* 定义展开时的回调函数
* @param event
*/
public void setPanelOpenedEvent(PanelOpenedEvent event)
{
this.panelOpenedEvent=event;
}
/**
* 把View放在Panel的Container
* @param v
*/
public void fillPanelContainer(View v)
{
panelContainer.addView(v);
}
/**
* 异步移动Panel
* @author hellogv
*/
class AsynMove extends AsyncTask<Integer, Integer, Void> {
@Override
protected Void doInBackground(Integer... params) {
int times;
if (mRightMargin % Math.abs(params[0]) == 0)// 整除
times = mRightMargin / Math.abs(params[0]);
else
// 有余数
times = mRightMargin / Math.abs(params[0]) + 1;
for (int i = 0; i < times; i++) {
publishProgress(params);
try {
Thread.sleep(Math.abs(params[0]));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... params) {
LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();
if (params[0] < 0)
lp.rightMargin = Math.max(lp.rightMargin + params[0],
(-mRightMargin));
else
lp.rightMargin = Math.min(lp.rightMargin + params[0], 0);
if(lp.rightMargin==0 && panelOpenedEvent!=null){//展开之后
panelOpenedEvent.onPanelOpened(Panel.this);//调用OPEN回调函数
}
else if(lp.rightMargin==-(mRightMargin) && panelClosedEvent!=null){//收缩之后
panelClosedEvent.onPanelClosed(Panel.this);//调用CLOSE回调函数
}
Panel.this.setLayoutParams(lp);
}
}
}
~~~
main.java是主控部分,演示了Panel的使用:
~~~
public class main extends Activity {
public Panel panel;
public LinearLayout container;
public GridView gridview;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
this.setTitle("“可动态布局”的抽屉组件之构建基础-----hellogv");
gridview = (GridView) findViewById(R.id.gridview);
container=(LinearLayout)findViewById(R.id.container);
panel=new Panel(this,gridview,200,LayoutParams.FILL_PARENT);
container.addView(panel);//加入Panel控件
//新建测试组件
TextView tvTest=new TextView(this);
tvTest.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
tvTest.setText("测试组件,红字白底");
tvTest.setTextColor(Color.RED);
tvTest.setBackgroundColor(Color.WHITE);
//加入到Panel里面
panel.fillPanelContainer(tvTest);
panel.setPanelClosedEvent(panelClosedEvent);
panel.setPanelOpenedEvent(panelOpenedEvent);
//往GridView填充测试数据
ArrayList<HashMap<String, Object>> lstImageItem = new ArrayList<HashMap<String, Object>>();
for (int i = 0; i < 100; i++) {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("ItemImage", R.drawable.icon);
map.put("ItemText", "NO." + String.valueOf(i));
lstImageItem.add(map);
}
SimpleAdapter saImageItems = new SimpleAdapter(this,
lstImageItem,
R.layout.item,
new String[] { "ItemImage", "ItemText" },
new int[] { R.id.ItemImage, R.id.ItemText });
gridview.setAdapter(saImageItems);
gridview.setOnItemClickListener(new ItemClickListener());
}
PanelClosedEvent panelClosedEvent =new PanelClosedEvent(){
@Override
public void onPanelClosed(View panel) {
Log.e("panelClosedEvent","panelClosedEvent");
}
};
PanelOpenedEvent panelOpenedEvent =new PanelOpenedEvent(){
@Override
public void onPanelOpened(View panel) {
Log.e("panelOpenedEvent","panelOpenedEvent");
}
};
class ItemClickListener implements OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> arg0,View arg1, int arg2, long arg3) {
@SuppressWarnings("unchecked")
HashMap<String, Object> item = (HashMap<String, Object>) arg0
.getItemAtPosition(arg2);
setTitle((String) item.get("ItemText"));
}
}
~~~
后面还会继续介绍如何在Panel加入拖拉效果的处理!
精确监听AbsListView滚动至底部
最后更新于:2022-04-01 15:47:40
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
用户使用android客户端时,当ListView滚动至底部,可以由一个按钮来提示用户是否读下一页,那么如果使用GridView呢?现在很多WEB 2.0上的体验就是当底部时自动读取下一页数据,GridView(ListView也可)可以采用这种方法。网上已经有很多文章介绍了如何判断ListView是否滚动至底部,原理是 AbsListView.getLastVisiblePosition() = (AbsListView.getCount() - 1) 即到底,如果往上拖一点,用户看起来已经离开底部,但实际上 AbsListView.getLastVisiblePosition() == (AbsListView.getCount() - 1) 依然成立,会导致误判断。本文在它们基础上加以改进,做到更精确地监听是否滚动至底部。先来看看本文程序运行的效果:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a7b46b5.gif)
本文在 [Android入门第八篇之GridView(九宫图)](http://blog.csdn.net/hellogv/article/details/4567095)基础上加入滚到事件判断,文件名为AutoLoadListener.java,原理是在AbsListView.getLastVisiblePosition() = =(AbsListView.getCount() - 1) 时,保存最后一个Item的绝对坐标,如果两次获取的绝对Y值都一样,即到底然后执行回调函数......源码如下:
~~~
package com.testScroll;
import android.util.Log;
import android.view.View;
import android.widget.AbsListView;
import android.widget.Toast;
import android.widget.AbsListView.OnScrollListener;
/**
* 滚动至列表底部,读取下一页数据
*/
public class AutoLoadListener implements OnScrollListener{
public interface AutoLoadCallBack {
void execute(String url);
}
private int getLastVisiblePosition = 0,lastVisiblePositionY=0;
private AutoLoadCallBack mCallback;
public AutoLoadListener(AutoLoadCallBack callback)
{
this.mCallback = callback;
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
//滚动到底部
if (view.getLastVisiblePosition() == (view.getCount() - 1)) {
View v=(View) view.getChildAt(view.getChildCount()-1);
int[] location = new int[2] ;
v.getLocationOnScreen(location);//获取在整个屏幕内的绝对坐标
int y=location [1];
Log.e("x"+location[0],"y"+location[1]);
if (view.getLastVisiblePosition()!=getLastVisiblePosition
&& lastVisiblePositionY!=y)//第一次拖至底部
{
Toast.makeText(view.getContext(), "再次拖至底部,即可翻页",500).show();
getLastVisiblePosition=view.getLastVisiblePosition();
lastVisiblePositionY=y;
return;
}
else if (view.getLastVisiblePosition()==getLastVisiblePosition
&& lastVisiblePositionY==y)//第二次拖至底部
{
mCallback.execute(">>>>>拖至底部");
}
}
//未滚动到底部,第二次拖至底部都初始化
getLastVisiblePosition=0;
lastVisiblePositionY=0;
}
}
public void onScroll(AbsListView arg0, int arg1, int arg2, int arg3) {
}
}
~~~
主程序为testScroll.java,源码如下:
~~~
package com.testScroll;
import java.util.ArrayList;
import java.util.HashMap;
import com.testScroll.AutoLoadListener.AutoLoadCallBack;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;
import android.widget.SimpleAdapter;
import android.widget.Toast;
public class testScroll extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setTitle("精确监听AbsListView滚动至底部----hellogv");
GridView gridview = (GridView) findViewById(R.id.gridview);
// 生成动态数组,并且转入数据
ArrayList<HashMap<String, Object>> lstImageItem = new ArrayList<HashMap<String, Object>>();
for (int i = 0; i < 30; i++) {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("ItemImage", R.drawable.icon);// 添加图像资源的ID
map.put("ItemText", "NO." + String.valueOf(i));// 按序号做ItemText
lstImageItem.add(map);
}
// 生成适配器的ImageItem <====> 动态数组的元素,两者一一对应
SimpleAdapter saImageItems = new SimpleAdapter(this, // 没什么解释
lstImageItem,// 数据来源
R.layout.night_item,// night_item的XML实现
// 动态数组与ImageItem对应的子项
new String[] { "ItemImage", "ItemText" },
// ImageItem的XML文件里面的一个ImageView,两个TextView ID
new int[] { R.id.ItemImage, R.id.ItemText });
//添加自动读页的事件
AutoLoadListener autoLoadListener =new AutoLoadListener(callBack);
gridview.setOnScrollListener(autoLoadListener);
// 添加并且显示
gridview.setAdapter(saImageItems);
// 添加消息处理
gridview.setOnItemClickListener(new ItemClickListener());
}
AutoLoadCallBack callBack=new AutoLoadCallBack(){
public void execute(String url) {
Toast.makeText(testScroll.this, url, 500).show();
}
};
// 当AdapterView被单击(触摸屏或者键盘),则返回的Item单击事件
class ItemClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> arg0,// The AdapterView where the
// click happened
View arg1,// The view within the AdapterView that was clicked
int arg2,// The position of the view in the adapter
long arg3// The row id of the item that was clicked
) {
// 在本例中arg2=arg3
HashMap<String, Object> item = (HashMap<String, Object>) arg0
.getItemAtPosition(arg2);
// 显示所选Item的ItemText
setTitle((String) item.get("ItemText"));
}
}
}
~~~
android平板上的GridView视图缓存优化
最后更新于:2022-04-01 15:47:38
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
最近在做android平板上的开发,其中涉及到高分辨率之下使用GridView的性能问题。在Android手机软件开发中,如果在ListView或者GridView上使用大数量Item,很多人都会想到ViewHolder......没错,ViewHolder非常适合用在ListView或者每行小于4个Item的GridView。但是如果是高分辨率的设备(android平板甚至android电视),每行包含4个以上Item的话,即使用了ViewHolder也依然卡。
如下图,每行9个Item,而且每个Item的图片都是从网络动态下载的,这时就比较考验GridView视图的优化了。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a7218e0.gif)
本文提出的优化方法是:在getView()构建一个View列表(List<View>),把最近构建的View存起来,回退时直接从View列表中读取,而不是动态构建。使用这种方法有2个好处:
**1.快速读取过去的Item;**
**2.直接保存View而不是Bitmap,避免了ImageView.setImageBitmaps()带来的延时。**
**当然坏处就是浪费内存,所以要设定一个上限,超过了就删掉最老的Item。
先来看看这种方法与ViewHolder的性能对比:**
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a767b3c.gif)
100个Item往下滚到的三组数据对比,如上图:
“CacheAdapter 缓存50个Item”跟ViewHolderAdapter的速度很接近,由于CacheAdapter有缓存,所以会有1~2次快速读取Item(10~20个)的情况,而ViewHolder的每次读取Item速度比较平均。
“CacheAdapter 缓存75个Item”只在第一次往下滚动时消耗较长时间,第二次用了缓存的Item,所以速度快了很多。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a78c8c7.gif)
100个Item往上滚到的三组数据对比,如上图:
“CacheAdapter 缓存50个Item”比ViewHolderAdapter的速度略快,“CacheAdapter 缓存75个Item”依然是最快的。
总结:“CacheAdapter 缓存50个Item”速度与HolderView略快,读取最近的Item速度最快,缓存的Item越多速度越快。“CacheAdapter 缓存75个Item”占用内存最少,这是由于一部分图片下载失败,保存的Item的图片为空,实际上是缓存越多Item占用的内存越多。
PS:这里用到异步读取网络图片,成功下载的就占用较多内存,下载失败就占用较少内存,所以内存占用情况并不是一个时刻的绝对值,占用内存只用于参考.....
本文程序源码可以到[http://www.rayfile.com/zh-cn/files/5ebf5666-958a-11e0-99ec-0015c55db73d/](http://www.rayfile.com/zh-cn/files/5ebf5666-958a-11e0-99ec-0015c55db73d/)这里下载。
CacheAdapter.java是实现缓存Item的自定义Adapter,源码如下:
~~~
/** * 使用列表缓存过去的Item * @author hellogv * */public class CacheAdapter extends BaseAdapter { public class Item { public String itemImageURL; public String itemTitle; public Item(String itemImageURL, String itemTitle) { this.itemImageURL = itemImageURL; this.itemTitle = itemTitle; } } private Context mContext; private ArrayList<Item> mItems = new ArrayList<Item>(); LayoutInflater inflater; public CacheAdapter(Context c) { mContext = c; inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } public void addItem(String itemImageURL, String itemTitle) { mItems.add(new Item(itemImageURL, itemTitle)); } public int getCount() { return mItems.size(); } public Item getItem(int position) { return mItems.get(position); } public long getItemId(int position) { return position; } List<Integer> lstPosition=new ArrayList<Integer>(); List<View> lstView=new ArrayList<View>(); List<Integer> lstTimes= new ArrayList<Integer>(); long startTime=0; public View getView(int position, View convertView, ViewGroup parent) { startTime=System.nanoTime(); if (lstPosition.contains(position) == false) { if(lstPosition.size()>75)//这里设置缓存的Item数量 { lstPosition.remove(0);//删除第一项 lstView.remove(0);//删除第一项 } convertView = inflater.inflate(R.layout.item, null); TextView text = (TextView) convertView.findViewById(R.id.itemText); ImageView icon = (ImageView) convertView.findViewById(R.id.itemImage); text.setText(mItems.get(position).itemTitle); new AsyncLoadImage().execute(new Object[] { icon,mItems.get(position).itemImageURL }); lstPosition.add(position);//添加最新项 lstView.add(convertView);//添加最新项 } else { convertView = lstView.get(lstPosition.indexOf(position)); } int endTime=(int) (System.nanoTime()-startTime); lstTimes.add(endTime); if(lstTimes.size()==10) { int total=0; for(int i=0;i<lstTimes.size();i++) total=total+lstTimes.get(i); Log.e("10个所花的时间:" +total/1000 +" μs", "所用内存:"+Runtime.getRuntime().totalMemory()/1024 +" KB"); lstTimes.clear(); } return convertView; } /** * 异步读取网络图片 * @author hellogv */ class AsyncLoadImage extends AsyncTask<Object, Object, Void> { @Override protected Void doInBackground(Object... params) { try { ImageView imageView=(ImageView) params[0]; String url=(String) params[1]; Bitmap bitmap = getBitmapByUrl(url); publishProgress(new Object[] {imageView, bitmap}); } catch (MalformedURLException e) { Log.e("error",e.getMessage()); e.printStackTrace(); } catch (IOException e) { Log.e("error",e.getMessage()); e.printStackTrace(); } return null; } protected void onProgressUpdate(Object... progress) { ImageView imageView = (ImageView) progress[0]; imageView.setImageBitmap((Bitmap) progress[1]); } } static public Bitmap getBitmapByUrl(String urlString) throws MalformedURLException, IOException { URL url = new URL(urlString); URLConnection connection = url.openConnection(); connection.setConnectTimeout(25000); connection.setReadTimeout(90000); Bitmap bitmap = BitmapFactory.decodeStream(connection.getInputStream()); return bitmap; }}
~~~
其中if(lstPosition.size()>75)是设置缓存的Item数量的关键地方,这里缓存75个Item。
ViewHolderAdapter.java是实现ViewHolder加载Item的自定义Adapter,源码如下:
~~~
/** * 使用ViewHolder加载Item * @author hellogv * */public class ViewHolderAdapter extends BaseAdapter { public class Item { public String itemImageURL; public String itemTitle; public Item(String itemImageURL, String itemTitle) { this.itemImageURL = itemImageURL; this.itemTitle = itemTitle; } } private Context mContext; private ArrayList<Item> mItems = new ArrayList<Item>(); LayoutInflater inflater; public ViewHolderAdapter(Context c) { mContext = c; inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } public void addItem(String itemImageURL, String itemTitle) { mItems.add(new Item(itemImageURL, itemTitle)); } public int getCount() { return mItems.size(); } public Item getItem(int position) { return mItems.get(position); } public long getItemId(int position) { return position; } static class ViewHolder { TextView text; ImageView icon; } List<Integer> lstTimes= new ArrayList<Integer>(); long startTime=0; public View getView(int position, View convertView, ViewGroup parent) { startTime=System.nanoTime(); ViewHolder holder; if (convertView == null) { convertView = inflater.inflate(R.layout.item, null); holder = new ViewHolder(); holder.text = (TextView) convertView.findViewById(R.id.itemText); holder.icon = (ImageView) convertView.findViewById(R.id.itemImage); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.text.setText(mItems.get(position).itemTitle); new AsyncLoadImage().execute(new Object[]{holder.icon,mItems.get(position).itemImageURL }); int endTime=(int) (System.nanoTime()-startTime); lstTimes.add(endTime); if(lstTimes.size()==10) { int total=0; for(int i=0;i<lstTimes.size();i++) total=total+lstTimes.get(i); Log.e("10个所花的时间:" +total/1000 +" μs", "所用内存:"+Runtime.getRuntime().totalMemory()/1024 +" KB"); lstTimes.clear(); } return convertView; } /** * 异步读取网络图片 * @author hellogv */ class AsyncLoadImage extends AsyncTask<Object, Object, Void> { @Override protected Void doInBackground(Object... params) { try { ImageView imageView=(ImageView) params[0]; String url=(String) params[1]; Bitmap bitmap = CacheAdapter.getBitmapByUrl(url); publishProgress(new Object[] {imageView, bitmap}); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } protected void onProgressUpdate(Object... progress) { ImageView imageView = (ImageView) progress[0]; imageView.setImageBitmap((Bitmap) progress[1]); } }}
~~~
testPerformance.java是主程序,通过注释符就可以分别测试CacheAdapter与ViewHolderAdapter的性能,源码如下:
~~~
public class testPerformance extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.setTitle("android平板上的GridView视图缓存优化-----hellogv"); GridView gridview = (GridView) findViewById(R.id.gridview); CacheAdapter adapter=new CacheAdapter(this); // ViewHolderAdapter adapter=new ViewHolderAdapter(this); gridview.setAdapter(adapter); String urlImage="";//请自己选择网络上的静态图片 for(int i=0;i<100;i++) { adapter.addItem(urlImage, "第"+i+"项"); } }}
~~~
Android提高第二十一篇之MediaPlayer播放网络视频
最后更新于:2022-04-01 15:47:35
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
上次讲解了[MediaPlayer播放网络音频](http://blog.csdn.net/hellogv/archive/2011/05/09/6406732.aspx),介绍了MediaPlayer关于网络音频的缓冲和进度条控制的方法,这次再讲解MediaPlayer播放网络视频。播放网络视频比播放网络音频多需要一个SurfaceView而已,已经熟悉MediaPlayer播放网络音频之后,相信大家对播放网络视频也能很快地掌握。先来看看本文程序运行截图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a6f3384.gif)
本文程序的视频来自[http://daily3gp.com](http://daily3gp.com),大家可以替换程序中的视频链接,试试其他影片。
main.xml的源码如下:
~~~
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="fill_parent" android:layout_width="fill_parent"> <SurfaceView android:id="@+id/surfaceView1" android:layout_height="fill_parent" android:layout_width="fill_parent"></SurfaceView> <LinearLayout android:layout_height="wrap_content" android:layout_width="fill_parent" android:layout_gravity="bottom" android:orientation="vertical"> <LinearLayout android:orientation="horizontal" android:layout_gravity="center_horizontal" android:layout_marginTop="4.0dip" android:layout_height="wrap_content" android:layout_width="wrap_content"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnPlayUrl" android:text="播放网络视频"></Button> <Button android:layout_height="wrap_content" android:id="@+id/btnPause" android:text="暂停" android:layout_width="80dip"></Button> <Button android:layout_height="wrap_content" android:layout_width="80dip" android:text="停止" android:id="@+id/btnStop"></Button> </LinearLayout> <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginBottom="20dip"> <SeekBar android:paddingRight="10dip" android:layout_gravity="center_vertical" android:paddingLeft="10dip" android:layout_weight="1.0" android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="@+id/skbProgress" android:max="100"></SeekBar> </LinearLayout> </LinearLayout></FrameLayout>
~~~
Player.java是本文的核心,Player.java实现了“进度条更新”、“数据缓冲”、“SurfaceHolder生命周期”等功能,其中“SurfaceHolder生命周期”是视频与音频播放的最大区别,通过surfaceCreated()、surfaceDestroyed()、surfaceChanged()可以创建/释放某些资源。下面这个地方需要注意一下:
videoWidth = mediaPlayer.getVideoWidth(); videoHeight = mediaPlayer.getVideoHeight(); if (videoHeight != 0 && videoWidth != 0) { arg0.start(); }
有些视频是android播放器不能播放的,不能播放时videoHeight=0,videoWidth=0,以此来判断是否播放视频。
Player.java源码如下:
~~~
package com.videoplayer;import java.io.IOException;import java.util.Timer;import java.util.TimerTask;import android.media.AudioManager;import android.media.MediaPlayer;import android.media.MediaPlayer.OnBufferingUpdateListener;import android.media.MediaPlayer.OnCompletionListener;import android.os.Handler;import android.os.Message;import android.util.Log;import android.view.SurfaceHolder;import android.view.SurfaceView;import android.widget.SeekBar;public class Player implements OnBufferingUpdateListener, OnCompletionListener, MediaPlayer.OnPreparedListener, SurfaceHolder.Callback { private int videoWidth; private int videoHeight; public MediaPlayer mediaPlayer; private SurfaceHolder surfaceHolder; private SeekBar skbProgress; private Timer mTimer=new Timer(); public Player(SurfaceView surfaceView,SeekBar skbProgress) { this.skbProgress=skbProgress; surfaceHolder=surfaceView.getHolder(); surfaceHolder.addCallback(this); surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mTimer.schedule(mTimerTask, 0, 1000); } /******************************************************* * 通过定时器和Handler来更新进度条 ******************************************************/ TimerTask mTimerTask = new TimerTask() { @Override public void run() { if(mediaPlayer==null) return; if (mediaPlayer.isPlaying() && skbProgress.isPressed() == false) { handleProgress.sendEmptyMessage(0); } } }; Handler handleProgress = new Handler() { public void handleMessage(Message msg) { int position = mediaPlayer.getCurrentPosition(); int duration = mediaPlayer.getDuration(); if (duration > 0) { long pos = skbProgress.getMax() * position / duration; skbProgress.setProgress((int) pos); } }; }; //***************************************************** public void play() { mediaPlayer.start(); } public void playUrl(String videoUrl) { try { mediaPlayer.reset(); mediaPlayer.setDataSource(videoUrl); mediaPlayer.prepare();//prepare之后自动播放 //mediaPlayer.start(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalStateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void pause() { mediaPlayer.pause(); } public void stop() { if (mediaPlayer != null) { mediaPlayer.stop(); mediaPlayer.release(); mediaPlayer = null; } } @Override public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { Log.e("mediaPlayer", "surface changed"); } @Override public void surfaceCreated(SurfaceHolder arg0) { try { mediaPlayer = new MediaPlayer(); mediaPlayer.setDisplay(surfaceHolder); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setOnBufferingUpdateListener(this); mediaPlayer.setOnPreparedListener(this); } catch (Exception e) { Log.e("mediaPlayer", "error", e); } Log.e("mediaPlayer", "surface created"); } @Override public void surfaceDestroyed(SurfaceHolder arg0) { Log.e("mediaPlayer", "surface destroyed"); } @Override /** * 通过onPrepared播放 */ public void onPrepared(MediaPlayer arg0) { videoWidth = mediaPlayer.getVideoWidth(); videoHeight = mediaPlayer.getVideoHeight(); if (videoHeight != 0 && videoWidth != 0) { arg0.start(); } Log.e("mediaPlayer", "onPrepared"); } @Override public void onCompletion(MediaPlayer arg0) { // TODO Auto-generated method stub } @Override public void onBufferingUpdate(MediaPlayer arg0, int bufferingProgress) { skbProgress.setSecondaryProgress(bufferingProgress); int currentProgress=skbProgress.getMax()*mediaPlayer.getCurrentPosition()/mediaPlayer.getDuration(); Log.e(currentProgress+"% play", bufferingProgress + "% buffer"); }}
~~~
test_videoplayer.java是主程序,负责调用Player类,其中关键部分是SeekBarChangeEvent这个SeekBar拖动的事件:SeekBar的Progress是0~SeekBar.getMax()之内的数,而MediaPlayer.seekTo()的参数是0~MediaPlayer.getDuration()之内数,所以MediaPlayer.seekTo()的参数是(progress/seekBar.getMax())*MediaPlayer.getDuration()。
test_videoplayer.java源码如下:
~~~
package com.videoplayer;import android.app.Activity;import android.content.pm.ActivityInfo;import android.net.Uri;import android.os.Bundle;import android.util.Log;import android.view.SurfaceView;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.SeekBar;public class test_videoplayer extends Activity { private SurfaceView surfaceView; private Button btnPause, btnPlayUrl, btnStop; private SeekBar skbProgress; private Player player; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); surfaceView = (SurfaceView) this.findViewById(R.id.surfaceView1); btnPlayUrl = (Button) this.findViewById(R.id.btnPlayUrl); btnPlayUrl.setOnClickListener(new ClickEvent()); btnPause = (Button) this.findViewById(R.id.btnPause); btnPause.setOnClickListener(new ClickEvent()); btnStop = (Button) this.findViewById(R.id.btnStop); btnStop.setOnClickListener(new ClickEvent()); skbProgress = (SeekBar) this.findViewById(R.id.skbProgress); skbProgress.setOnSeekBarChangeListener(new SeekBarChangeEvent()); player = new Player(surfaceView, skbProgress); } class ClickEvent implements OnClickListener { @Override public void onClick(View arg0) { if (arg0 == btnPause) { player.pause(); } else if (arg0 == btnPlayUrl) { String url="http://daily3gp.com/vids/family_guy_penis_car.3gp"; player.playUrl(url); } else if (arg0 == btnStop) { player.stop(); } } } class SeekBarChangeEvent implements SeekBar.OnSeekBarChangeListener { int progress; @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { // 原本是(progress/seekBar.getMax())*player.mediaPlayer.getDuration() this.progress = progress * player.mediaPlayer.getDuration() / seekBar.getMax(); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { // seekTo()的参数是相对与影片时间的数字,而不是与seekBar.getMax()相对的数字 player.mediaPlayer.seekTo(progress); } }}
~~~
Android提高第二十篇之MediaPlayer播放网络音频
最后更新于:2022-04-01 15:47:33
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
以前曾经地介绍过[MediaPlayer的基本用法](http://blog.csdn.net/hellogv/archive/2010/10/30/5975864.aspx),这里就深入地讲解MediaPlayer的在线播放功能。本文主要实现MediaPlayer在线播放音频的功能,由于在线视频播放比在线音频播放复杂,因此先介绍在线音频播放的实现,这样可以帮助大家逐步深入了解MediaPlayer的在线播放功能。先来看看本文程序运行的结果:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a6dd7a2.gif)
main.xml的源码如下:
~~~
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="fill_parent" android:layout_width="fill_parent"> <LinearLayout android:layout_height="wrap_content" android:layout_width="fill_parent" android:orientation="vertical" android:layout_gravity="top"> <LinearLayout android:orientation="horizontal" android:layout_gravity="center_horizontal" android:layout_marginTop="4.0dip" android:layout_height="wrap_content" android:layout_width="wrap_content"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnPlayUrl" android:text="播放网络音频"></Button> <Button android:layout_height="wrap_content" android:id="@+id/btnPause" android:text="暂停" android:layout_width="80dip"></Button> <Button android:layout_height="wrap_content" android:layout_width="80dip" android:text="停止" android:id="@+id/btnStop"></Button> </LinearLayout> <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginBottom="20dip"> <SeekBar android:paddingRight="10dip" android:layout_gravity="center_vertical" android:paddingLeft="10dip" android:layout_weight="1.0" android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="@+id/skbProgress" android:max="100"></SeekBar> </LinearLayout> </LinearLayout></FrameLayout>
~~~
Player.java是本文的核心,Player.java实现了“进度条更新”、“数据缓冲”等功能,虽然不是很复杂的功能,但却是非常有用的功能。Player.java源码如下:
~~~
package com.musicplayer;import java.io.IOException;import java.util.Timer;import java.util.TimerTask;import android.media.AudioManager;import android.media.MediaPlayer;import android.media.MediaPlayer.OnBufferingUpdateListener;import android.media.MediaPlayer.OnCompletionListener;import android.os.Handler;import android.os.Message;import android.util.Log;import android.widget.SeekBar;public class Player implements OnBufferingUpdateListener, OnCompletionListener, MediaPlayer.OnPreparedListener{ public MediaPlayer mediaPlayer; private SeekBar skbProgress; private Timer mTimer=new Timer(); public Player(SeekBar skbProgress) { this.skbProgress=skbProgress; try { mediaPlayer = new MediaPlayer(); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setOnBufferingUpdateListener(this); mediaPlayer.setOnPreparedListener(this); } catch (Exception e) { Log.e("mediaPlayer", "error", e); } mTimer.schedule(mTimerTask, 0, 1000); } /******************************************************* * 通过定时器和Handler来更新进度条 ******************************************************/ TimerTask mTimerTask = new TimerTask() { @Override public void run() { if(mediaPlayer==null) return; if (mediaPlayer.isPlaying() && skbProgress.isPressed() == false) { handleProgress.sendEmptyMessage(0); } } }; Handler handleProgress = new Handler() { public void handleMessage(Message msg) { int position = mediaPlayer.getCurrentPosition(); int duration = mediaPlayer.getDuration(); if (duration > 0) { long pos = skbProgress.getMax() * position / duration; skbProgress.setProgress((int) pos); } }; }; //***************************************************** public void play() { mediaPlayer.start(); } public void playUrl(String videoUrl) { try { mediaPlayer.reset(); mediaPlayer.setDataSource(videoUrl); mediaPlayer.prepare();//prepare之后自动播放 //mediaPlayer.start(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalStateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void pause() { mediaPlayer.pause(); } public void stop() { if (mediaPlayer != null) { mediaPlayer.stop(); mediaPlayer.release(); mediaPlayer = null; } } @Override /** * 通过onPrepared播放 */ public void onPrepared(MediaPlayer arg0) { arg0.start(); Log.e("mediaPlayer", "onPrepared"); } @Override public void onCompletion(MediaPlayer arg0) { Log.e("mediaPlayer", "onCompletion"); } @Override public void onBufferingUpdate(MediaPlayer arg0, int bufferingProgress) { skbProgress.setSecondaryProgress(bufferingProgress); int currentProgress=skbProgress.getMax()*mediaPlayer.getCurrentPosition()/mediaPlayer.getDuration(); Log.e(currentProgress+"% play", bufferingProgress + "% buffer"); }}
~~~
test_musicplayer.java是主程序,负责调用Player类,其中关键部分是SeekBarChangeEvent这个SeekBar拖动的事件:SeekBar的Progress是0~SeekBar.getMax()之内的数,而MediaPlayer.seekTo()的参数是0~MediaPlayer.getDuration()之内数,所以MediaPlayer.seekTo()的参数是(progress/seekBar.getMax())*player.mediaPlayer.getDuration()。
test_musicplayer.java源码如下:
~~~
package com.musicplayer;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.SeekBar;public class test_musicplayer extends Activity { private Button btnPause, btnPlayUrl, btnStop; private SeekBar skbProgress; private Player player; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.setTitle("在线音乐播放---hellogv编写"); btnPlayUrl = (Button) this.findViewById(R.id.btnPlayUrl); btnPlayUrl.setOnClickListener(new ClickEvent()); btnPause = (Button) this.findViewById(R.id.btnPause); btnPause.setOnClickListener(new ClickEvent()); btnStop = (Button) this.findViewById(R.id.btnStop); btnStop.setOnClickListener(new ClickEvent()); skbProgress = (SeekBar) this.findViewById(R.id.skbProgress); skbProgress.setOnSeekBarChangeListener(new SeekBarChangeEvent()); player = new Player(skbProgress); } class ClickEvent implements OnClickListener { @Override public void onClick(View arg0) { if (arg0 == btnPause) { player.pause(); } else if (arg0 == btnPlayUrl) { //在百度MP3里随便搜索到的,大家可以试试别的链接 String url="http://219.138.125.22/myweb/mp3/CMP3/JH19.MP3"; player.playUrl(url); } else if (arg0 == btnStop) { player.stop(); } } } class SeekBarChangeEvent implements SeekBar.OnSeekBarChangeListener { int progress; @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { // 原本是(progress/seekBar.getMax())*player.mediaPlayer.getDuration() this.progress = progress * player.mediaPlayer.getDuration() / seekBar.getMax(); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { // seekTo()的参数是相对与影片时间的数字,而不是与seekBar.getMax()相对的数字 player.mediaPlayer.seekTo(progress); } }}
~~~
Android提高第十九篇之"多方向"抽屉
最后更新于:2022-04-01 15:47:31
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
在android上要实现类似Launch的抽屉效果,大家一定首先会想起SlidingDrawer。SlidingDrawer是android官方控件之一,本文的主角不是它,而是民间的控件工具集合~~~android-misc-widgets。android-misc-widgets里面包含几个widget:Panel、SmoothButton、Switcher、VirtualKeyboard,还有一些动画特效,本文主要介绍抽屉容器Panel的用法。android-misc-widgets的google工程地址:[-widgets/](http://code.google.com/p/android-misc)[http://code.google.com/p/android-misc](http://code.google.com/p/android-misc-widgets/),工程代码中Panel的演示效果如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a6b128c.gif)
这个Panel控件可以轻易实现不同方向的抽屉效果,比SlidingDrawer有更强的扩展性!
在多次使用Panel的过程中,发现Panel有个bug,会间断性出现“闪烁”,也就是在onTouchListener里面的触发ACTION_DOWN后,抽屉瞬间弹出然后瞬间回收(版本日期为[Feb 3, 2009](http://code.google.com/p/android-misc-widgets/source/browse/trunk/android-misc-widgets/src/org/miscwidgets/widget/Panel.java))。把原Panel的OnTouchListener,即以下代码:
~~~
OnTouchListener touchListener = new OnTouchListener() { int initX; int initY; boolean setInitialPosition; public boolean onTouch(View v, MotionEvent event) { if (mState == State.ANIMATING) { // we are animating return false; }// Log.d(TAG, "state: " + mState + " x: " + event.getX() + " y: " + event.getY()); int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { if (mBringToFront) { bringToFront(); } initX = 0; initY = 0; if (mContent.getVisibility() == GONE) { // since we may not know content dimensions we use factors here if (mOrientation == VERTICAL) { initY = mPosition == TOP? -1 : 1; } else { initX = mPosition == LEFT? -1 : 1; } } setInitialPosition = true; } else { if (setInitialPosition) { // now we know content dimensions, so we multiply factors... initX *= mContentWidth; initY *= mContentHeight; // ... and set initial panel's position mGestureListener.setScroll(initX, initY); setInitialPosition = false; // for offsetLocation we have to invert values initX = -initX; initY = -initY; } // offset every ACTION_MOVE & ACTION_UP event event.offsetLocation(initX, initY); } if (!mGestureDetector.onTouchEvent(event)) { if (action == MotionEvent.ACTION_UP) { // tup up after scrolling post(startAnimation); } } return false; } };
~~~
替换为:
~~~
OnTouchListener touchListener = new OnTouchListener() { float touchX, touchY; public boolean onTouch(View v, MotionEvent event) { if (mState == State.ANIMATING) { // we are animating return false; } int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { if (mBringToFront) { bringToFront(); } touchX = event.getX(); touchY = event.getY(); } if (!mGestureDetector.onTouchEvent(event)) { if (action == MotionEvent.ACTION_UP) { // tup up after scrolling int size = (int) (Math.abs(touchX - event.getX()) + Math .abs(touchY - event.getY())); if (size == mContentWidth || size == mContentHeight) { mState = State.ABOUT_TO_ANIMATE; //Log.e("size", String.valueOf(size)); //Log.e(String.valueOf(mContentWidth),String.valueOf(mContentHeight)); } post(startAnimation); } } return false; } };
~~~
即可修复这个bug,并且也同样实现了OnClickListener的功能,可以把原Panel的OnClickListener给删掉了!
Android提高十八篇之自定义Menu(TabMenu)
最后更新于:2022-04-01 15:47:29
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
快要过年了,在这里先祝广大的技术宅兔年快乐!![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a62a647.gif)
用过UCWEB-Android版的人都应该对其特殊的menu有印象,把menu做成Tab-Menu(支持分页的Menu),可以容纳比Android传统的menu更丰富的内容(Android的menu超过6项则缩略在[更多]里),本文参考网上的例子(作者:CoffeeCole,email:longkefan@foxmail.com),对例子进行简化以及封装,使其作为一个复合控件融入自己的framework。
先来看看本文程序运行的效果:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a642d11.gif)
TabMenu本身就是一个PopupWindow,PopupWindow上面放了两个GridView,第一个GridView就是分页标签,位于PopupWindow的顶部,第二个GridView是菜单,位于PopupWindow的主体。为了实现PopupWindow的弹出/退出的动画效果,本文使用了以下代码:
在工程的res文件夹里添加anim子目录,再新建文件popup_enter.xml:
~~~
<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromYDelta="100%p" android:toYDelta="0" android:duration="1000" /> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="1000" /></set>
~~~
新建文件popup_exit.xml:
~~~
<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromYDelta="0" android:toYDelta="100%p" android:duration="1000" /> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="1000" /></set>
~~~
在工程的values文件夹里新建文件popup_animation.xml:
~~~
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="PopupAnimation" parent="android:Animation">
<item name="android:windowEnterAnimation">@anim/popup_enter</item>
<item name="android:windowExitAnimation">@anim/popup_exit</item>
</style>
</resources>
~~~
main.xml的源码如下:
~~~
<?xml version="1.0" encoding="utf-8"?><LinearLayout android:id="@+id/LinearLayout01" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <TextView android:id="@+id/TextView01" android:layout_height="wrap_content" android:layout_width="fill_parent" android:text="扩展Menu----hellogv"></TextView></LinearLayout>
~~~
TabMenu的封装类TabMenu.java的源码如下:
~~~
package com.testTabMenu;import android.content.Context;import android.graphics.Color;import android.graphics.drawable.ColorDrawable;import android.view.Gravity;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.GridView;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.PopupWindow;import android.widget.TextView;import android.widget.AdapterView.OnItemClickListener;import android.widget.LinearLayout.LayoutParams;public class TabMenu extends PopupWindow{ private GridView gvBody, gvTitle; private LinearLayout mLayout; private MenuTitleAdapter titleAdapter; public TabMenu(Context context,OnItemClickListener titleClick,OnItemClickListener bodyClick, MenuTitleAdapter titleAdapter,int colorBgTabMenu,int aniTabMenu){ super(context); mLayout = new LinearLayout(context); mLayout.setOrientation(LinearLayout.VERTICAL); //标题选项栏 gvTitle = new GridView(context); gvTitle.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); gvTitle.setNumColumns(titleAdapter.getCount()); gvTitle.setStretchMode(GridView.STRETCH_COLUMN_WIDTH); gvTitle.setVerticalSpacing(1); gvTitle.setHorizontalSpacing(1); gvTitle.setGravity(Gravity.CENTER); gvTitle.setOnItemClickListener(titleClick); gvTitle.setAdapter(titleAdapter); gvTitle.setSelector(new ColorDrawable(Color.TRANSPARENT));//选中的时候为透明色 this.titleAdapter=titleAdapter; //子选项栏 gvBody = new GridView(context); gvBody.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT)); gvBody.setSelector(new ColorDrawable(Color.TRANSPARENT));//选中的时候为透明色 gvBody.setNumColumns(4); gvBody.setStretchMode(GridView.STRETCH_COLUMN_WIDTH); gvBody.setVerticalSpacing(10); gvBody.setHorizontalSpacing(10); gvBody.setPadding(10, 10, 10, 10); gvBody.setGravity(Gravity.CENTER); gvBody.setOnItemClickListener(bodyClick); mLayout.addView(gvTitle); mLayout.addView(gvBody); //设置默认项 this.setContentView(mLayout); this.setWidth(LayoutParams.FILL_PARENT); this.setHeight(LayoutParams.WRAP_CONTENT); this.setBackgroundDrawable(new ColorDrawable(colorBgTabMenu));// 设置TabMenu菜单背景 this.setAnimationStyle(aniTabMenu); this.setFocusable(true);// menu菜单获得焦点 如果没有获得焦点menu菜单中的控件事件无法响应 } public void SetTitleSelect(int index) { gvTitle.setSelection(index); this.titleAdapter.SetFocus(index); } public void SetBodySelect(int index,int colorSelBody) { int count=gvBody.getChildCount(); for(int i=0;i<count;i++) { if(i!=index) ((LinearLayout)gvBody.getChildAt(i)).setBackgroundColor(Color.TRANSPARENT); } ((LinearLayout)gvBody.getChildAt(index)).setBackgroundColor(colorSelBody); } public void SetBodyAdapter(MenuBodyAdapter bodyAdapter) { gvBody.setAdapter(bodyAdapter); } /** * 自定义Adapter,TabMenu的每个分页的主体 * */ static public class MenuBodyAdapter extends BaseAdapter { private Context mContext; private int fontColor,fontSize; private String[] texts; private int[] resID; /** * 设置TabMenu的分页主体 * @param context 调用方的上下文 * @param texts 按钮集合的字符串数组 * @param resID 按钮集合的图标资源数组 * @param fontSize 按钮字体大小 * @param color 按钮字体颜色 */ public MenuBodyAdapter(Context context, String[] texts,int[] resID, int fontSize,int fontColor) { this.mContext = context; this.fontColor = fontColor; this.texts = texts; this.fontSize=fontSize; this.resID=resID; } public int getCount() { return texts.length; } public Object getItem(int position) { return makeMenyBody(position); } public long getItemId(int position) { return position; } private LinearLayout makeMenyBody(int position) { LinearLayout result=new LinearLayout(this.mContext); result.setOrientation(LinearLayout.VERTICAL); result.setGravity(Gravity.CENTER_HORIZONTAL|Gravity.CENTER_VERTICAL); result.setPadding(10, 10, 10, 10); TextView text = new TextView(this.mContext); text.setText(texts[position]); text.setTextSize(fontSize); text.setTextColor(fontColor); text.setGravity(Gravity.CENTER); text.setPadding(5, 5, 5, 5); ImageView img=new ImageView(this.mContext); img.setBackgroundResource(resID[position]); result.addView(img,new LinearLayout.LayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT))); result.addView(text); return result; } public View getView(int position, View convertView, ViewGroup parent) { return makeMenyBody(position); } } /** * 自定义Adapter,TabMenu的分页标签部分 * */ static public class MenuTitleAdapter extends BaseAdapter { private Context mContext; private int fontColor,unselcolor,selcolor; private TextView[] title; /** * 设置TabMenu的title * @param context 调用方的上下文 * @param titles 分页标签的字符串数组 * @param fontSize 字体大小 * @param fontcolor 字体颜色 * @param unselcolor 未选中项的背景色 * @param selcolor 选中项的背景色 */ public MenuTitleAdapter(Context context, String[] titles, int fontSize, int fontcolor,int unselcolor,int selcolor) { this.mContext = context; this.fontColor = fontcolor; this.unselcolor = unselcolor; this.selcolor=selcolor; this.title = new TextView[titles.length]; for (int i = 0; i < titles.length; i++) { title[i] = new TextView(mContext); title[i].setText(titles[i]); title[i].setTextSize(fontSize); title[i].setTextColor(fontColor); title[i].setGravity(Gravity.CENTER); title[i].setPadding(10, 10, 10, 10); } } public int getCount() { return title.length; } public Object getItem(int position) { return title[position]; } public long getItemId(int position) { return title[position].getId(); } /** * 设置选中的效果 */ private void SetFocus(int index) { for(int i=0;i<title.length;i++) { if(i!=index) { title[i].setBackgroundDrawable(new ColorDrawable(unselcolor));//设置没选中的颜色 title[i].setTextColor(fontColor);//设置没选中项的字体颜色 } } title[index].setBackgroundColor(0x00);//设置选中项的颜色 title[index].setTextColor(selcolor);//设置选中项的字体颜色 } public View getView(int position, View convertView, ViewGroup parent) { View v; if (convertView == null) { v = title[position]; } else { v = convertView; } return v; } }}
~~~
testTabMenu介绍了数据的定义以及TabMenu的使用,源码如下:
~~~
package com.testTabMenu;import android.app.Activity;import android.graphics.Color;import android.os.Bundle;import android.view.Gravity;import android.view.Menu;import android.view.View;import android.widget.AdapterView;import android.widget.AdapterView.OnItemClickListener;import android.widget.Toast;public class testTabMenu extends Activity { TabMenu.MenuBodyAdapter []bodyAdapter=new TabMenu.MenuBodyAdapter[3]; TabMenu.MenuTitleAdapter titleAdapter; TabMenu tabMenu; int selTitle=0; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //设置分页栏的标题 titleAdapter = new TabMenu.MenuTitleAdapter(this, new String[] { "常用", "设置", "工具" }, 16, 0xFF222222,Color.LTGRAY,Color.WHITE); //定义每项分页栏的内容 bodyAdapter[0]=new TabMenu.MenuBodyAdapter(this,new String[] { "常用1", "常用2", }, new int[] { R.drawable.menu_test, R.drawable.menu_bookmark},13, 0xFFFFFFFF); bodyAdapter[1]=new TabMenu.MenuBodyAdapter(this,new String[] { "设置1", "设置2", "设置3"}, new int[] { R.drawable.menu_edit, R.drawable.menu_delete, R.drawable.menu_fullscreen},13, 0xFFFFFFFF); bodyAdapter[2]=new TabMenu.MenuBodyAdapter(this,new String[] { "工具1", "工具2", "工具3", "工具4" }, new int[] { R.drawable.menu_copy, R.drawable.menu_cut, R.drawable.menu_normalmode, R.drawable.menu_quit },13, 0xFFFFFFFF); tabMenu=new TabMenu(this, new TitleClickEvent(), new BodyClickEvent(), titleAdapter, 0x55123456,//TabMenu的背景颜色 R.style.PopupAnimation);//出现与消失的动画 tabMenu.update(); tabMenu.SetTitleSelect(0); tabMenu.SetBodyAdapter(bodyAdapter[0]); } class TitleClickEvent implements OnItemClickListener{ @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { selTitle=arg2; tabMenu.SetTitleSelect(arg2); tabMenu.SetBodyAdapter(bodyAdapter[arg2]); } } class BodyClickEvent implements OnItemClickListener{ @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { tabMenu.SetBodySelect(arg2,Color.GRAY); String str="第"+String.valueOf(selTitle)+"栏/n/r" +"第"+String.valueOf(arg2)+"项"; Toast.makeText(testTabMenu.this, str, 500).show(); } } @Override /** * 创建MENU */ public boolean onCreateOptionsMenu(Menu menu) { menu.add("menu");// 必须创建一项 return super.onCreateOptionsMenu(menu); } @Override /** * 拦截MENU */ public boolean onMenuOpened(int featureId, Menu menu) { if (tabMenu != null) { if (tabMenu.isShowing()) tabMenu.dismiss(); else { tabMenu.showAtLocation(findViewById(R.id.LinearLayout01), Gravity.BOTTOM, 0, 0); } } return false;// 返回为true 则显示系统menu } }
~~~
Android-opencv之CVCamera
最后更新于:2022-04-01 15:47:26
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
[android-opencv](http://code.google.com/p/android-opencv/)是opencv在android手机上的移植版,而CVCamera是这个移植版的一个sample。本文主要介绍android-opencv的安装和使用。
**android-opencv的安装**
opencv基于C++,因此android-opencv也必须依赖NDK(android-ndk-r4-crystax)来编译。PS:关于android-ndk-r4-crystax和CYGWIN的安装和使用,本文不再唠叨,详见[http://blog.csdn.net/hellogv/archive/2010/12/23/6094127.aspx](http://blog.csdn.net/hellogv/archive/2010/12/23/6094127.aspx)
安装步骤具体如下:
1.
svn checkout [http://android-opencv.googlecode.com/svn/trunk/](http://android-opencv.googlecode.com/svn/trunk/), 下载源码
1.
确保在系统Path中包含了D:/cygwin/bin;D:/cygwin/android-ndk-r4-crystax;(存放目录自己决定,Path中必须包含cygwin的bin和android-ndk-r4-crystax的路径)
1.
再拷贝android-ndk-r4-crystax到/cygwin/home/GV/android-ndk-r4-crystax,编译android-opencv时需要,编译成功之后可以删除这份拷贝。
1.
运行cygwin,来到opencv目录下,输入sh build.sh进行编译,编译成功的话会在/opencv/android/libs/生成armeabi和armeabi-v7a两个文件夹,里面都包含libandroid-opencv.so。
PS:编译的时候提示缺少文件的话,从网上搜索下载。
-
**android-opencv的使用**
1.
打开eclipse ,Import Opencv这个工程,工程位于/opencv/android/ 。PS:如果也提示缺少文件,也需要从网上搜索下载
1.
Opencv这个工程编译通过之后,就可以Export它,选择JAVA的JAR file,导出时去掉[obj]和[libs]这两个文件夹,AndroidManifest.xml和default.properties,输出文件名为Opencv.jar,Export设置如下图:![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a5a8068.gif)
1.
下载swigwin-1.3.39,在系统path中加入D:/cygwin/swigwin-1.3.39(存放位置自定),重启
1.
打开cygwin,去到/samples/CVCamera/,输入sh build.sh,开始编译CVCamera的JNI,成功编译之后会生成libcvcamera.so
1.
把 /opencv/android/libs 复制到/samples/CVCamera/,因为CVCamera同时需要libandroid-opencv.so和libcvcamera.so
1.
Import CVCamera这个工程,加入Opencv.jar 这个Libraries
1.
编译CVCamera这个工程,生成CVCamera.apk(有6.06MB大小)
CVCamera程序运行截图如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a5cc94d.gif)
Android提高十七篇之多级树形菜单的实现
最后更新于:2022-04-01 15:47:24
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
在Android里要实现树形菜单,都是用ExpandableList(也有高手自己继承ListView或者LinearLayout来做),但是ExpandableList一般只能实现2级树形菜单......本文也依然使用ExpandableList,但是要实现的是3级树形菜单。本文程序运行效果图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a379fd4.gif)
当用BaseExpandableListAdapter来实现二级树形菜单时,父项(getGroupView())和子项(getChildView())都是使用TextView。当要实现三级树形菜单时,子项(getChildView())就必须使用ExpandableList了.......另外还要定义结构体来方便调用三级树形的数据,二级树形菜单可以用如下:
static public class TreeNode{ Object parent; List<Object> childs=new ArrayList<Object>(); }
三级树形菜单可以用如下,子项是二级树形菜单的结构体:
static public class SuperTreeNode { Object parent; //二级树形菜单的结构体 List<TreeViewAdapter.TreeNode> childs = new ArrayList<TreeViewAdapter.TreeNode>(); }
实现三级树形菜单有两点要注意的:
1、第二级也是个树形菜单,因此必须在第二级项目展开/回收时设置足够的空间来完全显示二级树形菜单;
2、在实现三级树形菜单时,发现菜单的方法都是用不了(如OnChildClickListener、OnGroupClickListener等),因此要获得选中的数据就必须在外部定义好回调函数,然后在第二级生成二级树形菜单时回调这个外部函数。
PS:本文在解决No.2关键点的时候,只能取得第三级选中的序号.....而第一,第二级依然无法获取其序号。
main.xml源码如下:
~~~
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:id="@+id/LinearLayout01" android:layout_width="wrap_content" android:layout_height="wrap_content"> <Button android:layout_height="wrap_content" android:text="两层结构" android:layout_width="160dip" android:id="@+id/btnNormal"></Button> <Button android:layout_height="wrap_content" android:text="三层结构" android:layout_width="160dip" android:id="@+id/btnSuper"></Button> </LinearLayout> <ExpandableListView android:id="@+id/ExpandableListView01" android:layout_width="fill_parent" android:layout_height="fill_parent"></ExpandableListView></LinearLayout>
~~~
testExpandableList.java是主类,调用其他工具类,源码如下:
~~~
package com.testExpandableList;import java.util.List;import android.app.Activity;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.ExpandableListView;import android.widget.ExpandableListView.OnChildClickListener;import android.widget.Toast;public class testExpandableList extends Activity { /** Called when the activity is first created. */ ExpandableListView expandableList; TreeViewAdapter adapter; SuperTreeViewAdapter superAdapter; Button btnNormal,btnSuper; // Sample data set. children[i] contains the children (String[]) for groups[i]. public String[] groups = { "xxxx好友", "xxxx同学", "xxxxx女人"}; public String[][] child= { { "A君", "B君", "C君", "D君" }, { "同学甲", "同学乙", "同学丙"}, { "御姐", "萝莉" } }; public String[] parent = { "xxxx好友", "xxxx同学"}; public String[][][] child_grandson= { {{"A君"}, {"AA","AAA"}}, {{"B君"}, {"BBB","BBBB","BBBBB"}}, {{"C君"}, {"CCC","CCCC"}}, {{"D君"}, {"DDD","DDDD","DDDDD"}}, }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.setTitle("ExpandableListView练习----hellogv"); btnNormal=(Button)this.findViewById(R.id.btnNormal); btnNormal.setOnClickListener(new ClickEvent()); btnSuper=(Button)this.findViewById(R.id.btnSuper); btnSuper.setOnClickListener(new ClickEvent()); adapter=new TreeViewAdapter(this,TreeViewAdapter.PaddingLeft>>1); superAdapter=new SuperTreeViewAdapter(this,stvClickEvent); expandableList=(ExpandableListView) testExpandableList.this.findViewById(R.id.ExpandableListView01); } class ClickEvent implements View.OnClickListener{ @Override public void onClick(View v) { adapter.RemoveAll(); adapter.notifyDataSetChanged(); superAdapter.RemoveAll(); superAdapter.notifyDataSetChanged(); if(v==btnNormal) { List<TreeViewAdapter.TreeNode> treeNode = adapter.GetTreeNode(); for(int i=0;i<groups.length;i++) { TreeViewAdapter.TreeNode node=new TreeViewAdapter.TreeNode(); node.parent=groups[i]; for(int ii=0;ii<child[i].length;ii++) { node.childs.add(child[i][ii]); } treeNode.add(node); } adapter.UpdateTreeNode(treeNode); expandableList.setAdapter(adapter); expandableList.setOnChildClickListener(new OnChildClickListener(){ @Override public boolean onChildClick(ExpandableListView arg0, View arg1, int parent, int children, long arg4) { String str="parent id:"+String.valueOf(parent)+",children id:"+String.valueOf(children); Toast.makeText(testExpandableList.this, str, 300).show(); return false; } }); } else if(v==btnSuper){ List<SuperTreeViewAdapter.SuperTreeNode> superTreeNode = superAdapter.GetTreeNode(); for(int i=0;i<parent.length;i++)//第一层 { SuperTreeViewAdapter.SuperTreeNode superNode=new SuperTreeViewAdapter.SuperTreeNode(); superNode.parent=parent[i]; //第二层 for(int ii=0;ii<child_grandson.length;ii++) { TreeViewAdapter.TreeNode node=new TreeViewAdapter.TreeNode(); node.parent=child_grandson[ii][0][0];//第二级菜单的标题 for(int iii=0;iii<child_grandson[ii][1].length;iii++)//第三级菜单 { node.childs.add(child_grandson[ii][1][iii]); } superNode.childs.add(node); } superTreeNode.add(superNode); } superAdapter.UpdateTreeNode(superTreeNode); expandableList.setAdapter(superAdapter); } } } /** * 三级树形菜单的事件不再可用,本函数由三级树形菜单的子项(二级菜单)进行回调 */ OnChildClickListener stvClickEvent=new OnChildClickListener(){ @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { String str="parent id:"+String.valueOf(groupPosition)+",children id:"+String.valueOf(childPosition); Toast.makeText(testExpandableList.this, str, 300).show(); return false; } };}
~~~
TreeViewAdapter.java是实现二级树形菜单的工具类,源码如下:
~~~
package com.testExpandableList;import java.util.ArrayList;import java.util.List;import android.content.Context;import android.util.Log;import android.view.Gravity;import android.view.View;import android.view.ViewGroup;import android.widget.AbsListView;import android.widget.BaseExpandableListAdapter;import android.widget.TextView;public class TreeViewAdapter extends BaseExpandableListAdapter{ public static final int ItemHeight=48;//每项的高度 public static final int PaddingLeft=36;//每项的高度 private int myPaddingLeft=0;//如果是由SuperTreeView调用,则作为子项需要往右移 static public class TreeNode{ Object parent; List<Object> childs=new ArrayList<Object>(); } List<TreeNode> treeNodes = new ArrayList<TreeNode>(); Context parentContext; public TreeViewAdapter(Context view,int myPaddingLeft) { parentContext=view; this.myPaddingLeft=myPaddingLeft; } public List<TreeNode> GetTreeNode() { return treeNodes; } public void UpdateTreeNode(List<TreeNode> nodes) { treeNodes=nodes; } public void RemoveAll() { treeNodes.clear(); } public Object getChild(int groupPosition, int childPosition) { return treeNodes.get(groupPosition).childs.get(childPosition); } public int getChildrenCount(int groupPosition) { return treeNodes.get(groupPosition).childs.size(); } static public TextView getTextView(Context context) { AbsListView.LayoutParams lp = new AbsListView.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ItemHeight); TextView textView = new TextView(context); textView.setLayoutParams(lp); textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); return textView; } public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { TextView textView = getTextView(this.parentContext); textView.setText(getChild(groupPosition, childPosition).toString()); textView.setPadding(myPaddingLeft+PaddingLeft, 0, 0, 0); return textView; } public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { TextView textView = getTextView(this.parentContext); textView.setText(getGroup(groupPosition).toString()); textView.setPadding(myPaddingLeft+(PaddingLeft>>1), 0, 0, 0); return textView; } public long getChildId(int groupPosition, int childPosition) { return childPosition; } public Object getGroup(int groupPosition) { return treeNodes.get(groupPosition).parent; } public int getGroupCount() { return treeNodes.size(); } public long getGroupId(int groupPosition) { return groupPosition; } public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } public boolean hasStableIds() { return true; }}
~~~
SuperTreeViewAdapter.java是实现三级树形菜单的工具类,会用到TreeViewAdapter.java,源码如下:
~~~
package com.testExpandableList;import java.util.ArrayList;import java.util.List;import com.testExpandableList.TreeViewAdapter.TreeNode;import android.content.Context;import android.view.View;import android.view.ViewGroup;import android.widget.AbsListView;import android.widget.BaseExpandableListAdapter;import android.widget.ExpandableListView;import android.widget.ExpandableListView.OnChildClickListener;import android.widget.ExpandableListView.OnGroupCollapseListener;import android.widget.ExpandableListView.OnGroupExpandListener;import android.widget.TextView;public class SuperTreeViewAdapter extends BaseExpandableListAdapter { static public class SuperTreeNode { Object parent; //二级树形菜单的结构体 List<TreeViewAdapter.TreeNode> childs = new ArrayList<TreeViewAdapter.TreeNode>(); } private List<SuperTreeNode> superTreeNodes = new ArrayList<SuperTreeNode>(); private Context parentContext; private OnChildClickListener stvClickEvent;//外部回调函数 public SuperTreeViewAdapter(Context view,OnChildClickListener stvClickEvent) { parentContext = view; this.stvClickEvent=stvClickEvent; } public List<SuperTreeNode> GetTreeNode() { return superTreeNodes; } public void UpdateTreeNode(List<SuperTreeNode> node) { superTreeNodes = node; } public void RemoveAll() { superTreeNodes.clear(); } public Object getChild(int groupPosition, int childPosition) { return superTreeNodes.get(groupPosition).childs.get(childPosition); } public int getChildrenCount(int groupPosition) { return superTreeNodes.get(groupPosition).childs.size(); } public ExpandableListView getExpandableListView() { AbsListView.LayoutParams lp = new AbsListView.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, TreeViewAdapter.ItemHeight); ExpandableListView superTreeView = new ExpandableListView(parentContext); superTreeView.setLayoutParams(lp); return superTreeView; } /** * 三层树结构中的第二层是一个ExpandableListView */ public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { // 是 final ExpandableListView treeView = getExpandableListView(); final TreeViewAdapter treeViewAdapter = new TreeViewAdapter(this.parentContext,0); List<TreeNode> tmp = treeViewAdapter.GetTreeNode();//临时变量取得TreeViewAdapter的TreeNode集合,可为空 final TreeNode treeNode=(TreeNode) getChild(groupPosition, childPosition); tmp.add(treeNode); treeViewAdapter.UpdateTreeNode(tmp); treeView.setAdapter(treeViewAdapter); //关键点:取得选中的二级树形菜单的父子节点,结果返回给外部回调函数 treeView.setOnChildClickListener(this.stvClickEvent); /** * 关键点:第二级菜单展开时通过取得节点数来设置第三级菜单的大小 */ treeView.setOnGroupExpandListener(new OnGroupExpandListener() { @Override public void onGroupExpand(int groupPosition) { AbsListView.LayoutParams lp = new AbsListView.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, (treeNode.childs.size()+1)*TreeViewAdapter.ItemHeight + 10); treeView.setLayoutParams(lp); } }); /** * 第二级菜单回收时设置为标准Item大小 */ treeView.setOnGroupCollapseListener(new OnGroupCollapseListener() { @Override public void onGroupCollapse(int groupPosition) { AbsListView.LayoutParams lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, TreeViewAdapter.ItemHeight); treeView.setLayoutParams(lp); } }); treeView.setPadding(TreeViewAdapter.PaddingLeft, 0, 0, 0); return treeView; } /** * 三级树结构中的首层是TextView,用于作为title */ public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { TextView textView = TreeViewAdapter.getTextView(this.parentContext); textView.setText(getGroup(groupPosition).toString()); textView.setPadding(TreeViewAdapter.PaddingLeft, 0, 0, 0); return textView; } public long getChildId(int groupPosition, int childPosition) { return childPosition; } public Object getGroup(int groupPosition) { return superTreeNodes.get(groupPosition).parent; } public int getGroupCount() { return superTreeNodes.size(); } public long getGroupId(int groupPosition) { return groupPosition; } public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } public boolean hasStableIds() { return true; }}
~~~
总结,使用ExpandableList实现三级树形菜单时有些bug不好解决,而且定义三维数组的时候也要倍加小心......所以尽量把数据化简来使用二级树形菜单。
在Android上使用ZXing识别条形码/二维码
最后更新于:2022-04-01 15:47:22
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
越来越多的手机具备自动对焦的拍摄功能,这也意味着这些手机可以具备条码扫描的功能.......手机具备条码扫描的功能,可以优化购物流程,快速存储电子名片(二维码)等。
本文使用ZXing 1.6实现条码/二维码识别。[ZXing](http://code.google.com/p/zxing/)是个很经典的条码/二维码识别的开源类库,long long ago,就有开发者在J2ME上使用ZXing了,不过要支持JSR-234规范(自动对焦)的手机才能发挥其威力,而目前已经有不少Android手机具备自动对焦的功能。
本文代码运行的结果如下,使用91手机助手截图时,无法截取SurfaceView的实时图像:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a2ef3a5.gif)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a31786c.gif)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a32af15.gif)
本文使用了ZXing1.6的core,即把/zxing-1.6/core/下的src复制覆盖工程的src;另外还要使用到/zxing-1.6/android/下的PlanarYUVLuminanceSource.java。
PS:/zxing-1.6/android/ 是BarcodeScanner的源码,本文程序相当于BarcodeScanner的精简版,只保留最基本的识别功能。
本文源码工程的下载地址如下:[http://www.pudn.com/downloads349/sourcecode/comm/android/detail1521939.html](http://www.pudn.com/downloads349/sourcecode/comm/android/detail1521939.html)
源码目录结果如下图,ChecksumException.java下面还有很多源文件,截图尚未列出:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a359ad8.gif)
本文例子必须要开摄像头和自动对焦的权限,不然启动时会报异常,所用的权限如下:
<uses-permission android:name="android.permission.CAMERA"></uses-permission><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission><uses-feature android:name="android.hardware.camera" /><uses-feature android:name="android.hardware.camera.autofocus" />
main.xml源码如下,main.xml必须要用到FrameLayout才能重叠控件实现“范围框”的效果:
~~~
<?xml version="1.0" encoding="utf-8"?><FrameLayout android:id="@+id/FrameLayout01" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <SurfaceView android:layout_height="fill_parent" android:id="@+id/sfvCamera" android:layout_width="fill_parent"></SurfaceView> <RelativeLayout android:id="@+id/RelativeLayout01" android:layout_height="fill_parent" android:layout_width="fill_parent"> <ImageView android:id="@+id/ImageView01" android:layout_height="100dip" android:layout_width="160dip"></ImageView> <View android:layout_centerVertical="true" android:layout_centerHorizontal="true" android:layout_width="300dip" android:background="#55FF6666" android:id="@+id/centerView" android:layout_height="180dip"></View> <TextView android:layout_centerHorizontal="true" android:layout_width="wrap_content" android:layout_below="@+id/centerView" android:layout_height="wrap_content" android:text="Scanning..." android:id="@+id/txtScanResult" android:textColor="#FF000000"></TextView> </RelativeLayout></FrameLayout>
~~~
testCamera.java是主类,负责控制Camera和对图像做解码,源码如下:
~~~
package com.testCamera;import java.util.Timer;import java.util.TimerTask;import com.google.zxing.BinaryBitmap;import com.google.zxing.MultiFormatReader;import com.google.zxing.Result;import com.google.zxing.Android.PlanarYUVLuminanceSource;import com.google.zxing.common.HybridBinarizer;import android.app.Activity;import android.graphics.Bitmap;import android.hardware.Camera;import android.os.Bundle;import android.view.SurfaceView;import android.view.View;import android.widget.ImageView;import android.widget.TextView;public class testCamera extends Activity { /** Called when the activity is first created. */ private SurfaceView sfvCamera; private SFHCamera sfhCamera; private ImageView imgView; private View centerView; private TextView txtScanResult; private Timer mTimer; private MyTimerTask mTimerTask; // 按照标准HVGA final static int width = 480; final static int height = 320; int dstLeft, dstTop, dstWidth, dstHeight; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.setTitle("Android条码/二维码识别Demo-----hellogv"); imgView = (ImageView) this.findViewById(R.id.ImageView01); centerView = (View) this.findViewById(R.id.centerView); sfvCamera = (SurfaceView) this.findViewById(R.id.sfvCamera); sfhCamera = new SFHCamera(sfvCamera.getHolder(), width, height, previewCallback); txtScanResult=(TextView)this.findViewById(R.id.txtScanResult); // 初始化定时器 mTimer = new Timer(); mTimerTask = new MyTimerTask(); mTimer.schedule(mTimerTask, 0, 80); } class MyTimerTask extends TimerTask { @Override public void run() { if (dstLeft == 0) {//只赋值一次 dstLeft = centerView.getLeft() * width / getWindowManager().getDefaultDisplay().getWidth(); dstTop = centerView.getTop() * height / getWindowManager().getDefaultDisplay().getHeight(); dstWidth = (centerView.getRight() - centerView.getLeft())* width / getWindowManager().getDefaultDisplay().getWidth(); dstHeight = (centerView.getBottom() - centerView.getTop())* height / getWindowManager().getDefaultDisplay().getHeight(); } sfhCamera.AutoFocusAndPreviewCallback(); } } /** * 自动对焦后输出图片 */ private Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera arg1) { //取得指定范围的帧的数据 PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource( data, width, height, dstLeft, dstTop, dstWidth, dstHeight); //取得灰度图 Bitmap mBitmap = source.renderCroppedGreyscaleBitmap(); //显示灰度图 imgView.setImageBitmap(mBitmap); BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); MultiFormatReader reader = new MultiFormatReader(); try { Result result = reader.decode(bitmap); String strResult = "BarcodeFormat:" + result.getBarcodeFormat().toString() + " text:" + result.getText(); txtScanResult.setText(strResult); } catch (Exception e) { txtScanResult.setText("Scanning"); } } };}
~~~
SFHCamera.java是Camera控制类,源码如下:
~~~
package com.testCamera;import java.io.IOException;import android.graphics.PixelFormat;import android.hardware.Camera;import android.util.Log;import android.view.SurfaceHolder;public class SFHCamera implements SurfaceHolder.Callback{ private SurfaceHolder holder = null; private Camera mCamera; private int width,height; private Camera.PreviewCallback previewCallback; public SFHCamera(SurfaceHolder holder,int w,int h,Camera.PreviewCallback previewCallback) { this.holder = holder; this.holder.addCallback(this); this.holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); width=w; height=h; this.previewCallback=previewCallback; } @Override public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { Camera.Parameters parameters = mCamera.getParameters(); parameters.setPreviewSize(width, height);//设置尺寸 parameters.setPictureFormat(PixelFormat.JPEG); mCamera.setParameters(parameters); mCamera.startPreview();//开始预览 Log.e("Camera","surfaceChanged"); } @Override public void surfaceCreated(SurfaceHolder arg0) { mCamera = Camera.open();//启动服务 try { mCamera.setPreviewDisplay(holder);//设置预览 Log.e("Camera","surfaceCreated"); } catch (IOException e) { mCamera.release();//释放 mCamera = null; } } @Override public void surfaceDestroyed(SurfaceHolder arg0) { mCamera.setPreviewCallback(null); mCamera.stopPreview();//停止预览 mCamera = null; Log.e("Camera","surfaceDestroyed"); } /** * 自动对焦并回调Camera.PreviewCallback */ public void AutoFocusAndPreviewCallback() { if(mCamera!=null) mCamera.autoFocus(mAutoFocusCallBack); } /** * 自动对焦 */ private Camera.AutoFocusCallback mAutoFocusCallBack = new Camera.AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { if (success) { //对焦成功,回调Camera.PreviewCallback mCamera.setOneShotPreviewCallback(previewCallback); } } }; }
~~~
其中testCamera.java的Camera.PreviewCallback previewCallback 是整个程序的逻辑核心,作为回调函数给SFHCamera.java的内部Camera类调用。
Android上使用ASIFT实现对视角变化更鲁棒的特征匹配
最后更新于:2022-04-01 15:47:19
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
今晚是平安夜,跟众多四眼技术宅一样,这个时候还是跟电脑过节![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a29f6c3.gif)
......
上次讲解了在[Android上通过NDK把彩图转换为灰度图](http://blog.csdn.net/hellogv/archive/2010/12/23/6094127.aspx),现在可以把[WindowsMobile版的ASIFT 例子](http://blog.csdn.net/hellogv/archive/2010/12/20/6087937.aspx)移植到Android上了.......在这里还是要再次感谢Jean-Michel Morel和Guoshen Yu两位大牛的无私奉献,尊重知识尊重开源精神。
先来看看本文程序运行截图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a2b3fc6.gif)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a2d2335.gif)
左图是设定识别率为最低的结果,右图是设定识别率为较低的结果。
本文的代码可以到这里下载:[http://www.pudn.com/downloads314/sourcecode/comm/android/detail1391871.html](http://www.pudn.com/downloads314/sourcecode/comm/android/detail1391871.html)
这里ASIFT的NDK代码(C++)跟WM篇的DLL代码大体一样,不过也存在一些不同:
1、JNI不支持引用传递,所以有些值必须通过函数返回,例如:
~~~
/** * 取得放大/缩小之后的图像大小 */JNIEXPORT jintArray JNICALL Java_com_testASIFT_LibASIFT_GetZoomSize( JNIEnv* env, jobject obj) { jint arrint[2]; arrint[0] = IM_X; arrint[1] = IM_Y; jintArray result = env->NewIntArray(2); env->SetIntArrayRegion(result, 0, 2, arrint); return result;}/** * 返回匹配后图像的大小 jintArray[0]为width, jintArray[1]为height */JNIEXPORT jintArray JNICALL Java_com_testASIFT_LibASIFT_GetMatchedImageSize( JNIEnv* env, jobject obj) { jint arrint[2]; arrint[0] = wo; arrint[1] = ho; jintArray result = env->NewIntArray(2); env->SetIntArrayRegion(result, 0, 2, arrint); return result;}
~~~
2、ASIFT接受的是8bit的灰度图,使用前要转换为8bit的灰度图:
~~~
void PixelToVector(jint *cbuf, int w, int h, std::vector<float> *ipixels) { for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { // 获得像素的颜色 int color = cbuf[w * i + j]; int red = ((color & 0x00FF0000) >> 16); int green = ((color & 0x0000FF00) >> 8); int blue = color & 0x000000FF; color = (red + green + blue) / 3; ipixels->push_back(color);//保存灰度值 } }}
~~~
使用后要把8bit灰度图转为RGB565:
~~~
jintArray result = env->NewIntArray(wo * ho); jint *cResult; cResult = env->GetIntArrayElements(result, false); int alpha = 0xFF << 24; for (int i = 0; i < ho; i++) { for (int j = 0; j < wo; j++) { // 获得像素的颜色 int color = (int) opixelsASIFT[wo * i + j]; color = alpha | (color << 16) | (color << 8) | color; cResult[wo * i + j] = color; } } env->ReleaseIntArrayElements(result, cResult, 0);
~~~
主类testASIFT.java的逻辑代码如下:
~~~
public class testASIFT extends Activity { /** Called when the activity is first created. */ ImageView imgView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.setTitle("Android上使用ASIFT---hellogv"); imgView=(ImageView)this.findViewById(R.id.ImageView01); LibASIFT.initZoomSize(320, 480);//缩放目标的大小 int []size=LibASIFT.GetZoomSize();//判断是否设置成功 Log.e(String.valueOf(size[0]),String.valueOf(size[1])); Bitmap img1=((BitmapDrawable) getResources().getDrawable(R.drawable.adam1)).getBitmap(); int w1=img1.getWidth(),h1=img1.getHeight(); int[] pix1 = new int[w1 * h1]; img1.getPixels(pix1, 0, w1, 0, 0, w1, h1); //提取第一张图片的特征点 LibASIFT.initImage1(pix1, w1, h1, 2); Bitmap img2=((BitmapDrawable) getResources().getDrawable(R.drawable.adam2)).getBitmap(); int w2=img2.getWidth(),h2=img2.getHeight(); int[] pix2 = new int[w2 * h2]; img2.getPixels(pix2, 0, w2, 0, 0, w2, h2); int[] imgPixels=LibASIFT.Match2ImageForImg(pix2, w2, h2, 2);//两图匹配 int[] imgSize=LibASIFT.GetMatchedImageSize();//匹配结果图的大小 Bitmap imgResult=Bitmap.createBitmap(imgSize[0], imgSize[1], Config.RGB_565); imgResult.setPixels(imgPixels, 0, imgResult.getWidth(), 0, 0, imgResult.getWidth(), imgResult.getHeight()); imgView.setImageBitmap(imgResult);//显示结果 }}
~~~
Android提高十六篇之使用NDK把彩图转换灰度图
最后更新于:2022-04-01 15:47:17
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
在Android上使用JAVA实现彩图转换为灰度图,跟J2ME上的实现类似,不过遇到频繁地转换或者是大图转换时,就必须使用NDK来提高速度了。本文主要通过JAVA和NDK这两种方式来分别实现彩图转换为灰度图,并给出速度的对比。
先来简单地介绍一下Android的NDK使用步骤:
以NDK r4为例,或许以后新版的NDK的使用方法略有不同。
1、下载支持C++的android-ndk-r4-crystax,支持C++的话可玩性更强......
2、下载cygwin,选择[ftp://mirrors.kernel.org](#)这个镜像,搜索 Devel Install 安装 gcc 和 make 等工具;
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a265ef3.gif)
在搜索框里分别搜索gcc和make,必须是 Devel Install 栏的。
3、Cygwin安装目录下,找到home/username的目录下的.bash_profile文件,打开文件在最后加上:
NDK=/cygdrive/d:cygwin/android-ndk-r4-crystax
export NDK
PS:假设安装在D:/cygwin/android-ndk-r4-crystax。
4、运行cygwin,通过cd命令去到NDK/samples/例子目录/,运行$NDK/ndk-build来编译该目录下的Android.mk
**以下是个人习惯.......**
5、安装Eclipse的CDT,官方下载cdt安装包,解压缩后把plugins和feagures 复制覆盖到eclipse文件夹下即可
6、去到系统属性->环境变量->Path添加"D:/cygwin/bin"(假设cygwin安装在D:下)和"D:/cygwin/android-ndk-r4-crystax",重启计算机,然后就可以在Eclipse里面建立基于cygwin的C/C++工程了,先通过这一步来验证NDK的程序能够编译成功,然后再通过第4步来生成SO文件。
接下来看看本文程序运行的效果:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a27f92a.gif)
从转换灰度图的耗时来说,NDK的确比JAVA所用的时间短不少。
main.xml源码如下:
~~~
<?xml version="1.0" encoding="utf-8" ?> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Button android:layout_height="wrap_content" android:layout_width="fill_parent" android:id="@+id/btnJAVA" android:text="使用JAVA转换灰度图" /> <Button android:layout_height="wrap_content" android:layout_width="fill_parent" android:id="@+id/btnNDK" android:text="使用NDK转换灰度图" /> <ImageView android:id="@+id/ImageView01" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>
~~~
主程序testToGray.java的源码如下:
~~~
package com.testToGray;import android.app.Activity;import android.graphics.Bitmap;import android.graphics.Bitmap.Config;import android.graphics.drawable.BitmapDrawable;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.ImageView;public class testToGray extends Activity { /** Called when the activity is first created. */ Button btnJAVA,btnNDK; ImageView imgView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.setTitle("使用NDK转换灰度图---hellogv"); btnJAVA=(Button)this.findViewById(R.id.btnJAVA); btnJAVA.setOnClickListener(new ClickEvent()); btnNDK=(Button)this.findViewById(R.id.btnNDK); btnNDK.setOnClickListener(new ClickEvent()); imgView=(ImageView)this.findViewById(R.id.ImageView01); } class ClickEvent implements View.OnClickListener{ @Override public void onClick(View v) { if(v==btnJAVA) { long current=System.currentTimeMillis(); Bitmap img=ConvertGrayImg(R.drawable.cat); long performance=System.currentTimeMillis()-current; //显示灰度图 imgView.setImageBitmap(img); testToGray.this.setTitle("w:"+String.valueOf(img.getWidth())+",h:"+String.valueOf(img.getHeight()) +" JAVA耗时 "+String.valueOf(performance)+" 毫秒"); } else if(v==btnNDK) { long current=System.currentTimeMillis(); //先打开图像并读取像素 Bitmap img1=((BitmapDrawable) getResources().getDrawable(R.drawable.cat)).getBitmap(); int w=img1.getWidth(),h=img1.getHeight(); int[] pix = new int[w * h]; img1.getPixels(pix, 0, w, 0, 0, w, h); //通过ImgToGray.so把彩色像素转为灰度像素 int[] resultInt=LibFuns.ImgToGray(pix, w, h); Bitmap resultImg=Bitmap.createBitmap(w, h, Config.RGB_565); resultImg.setPixels(resultInt, 0, w, 0, 0,w, h); long performance=System.currentTimeMillis()-current; //显示灰度图 imgView.setImageBitmap(resultImg); testToGray.this.setTitle("w:"+String.valueOf(img1.getWidth())+",h:"+String.valueOf(img1.getHeight()) +" NDK耗时 "+String.valueOf(performance)+" 毫秒"); } } } /** * 把资源图片转为灰度图 * @param resID 资源ID * @return */ public Bitmap ConvertGrayImg(int resID) { Bitmap img1=((BitmapDrawable) getResources().getDrawable(resID)).getBitmap(); int w=img1.getWidth(),h=img1.getHeight(); int[] pix = new int[w * h]; img1.getPixels(pix, 0, w, 0, 0, w, h); int alpha=0xFF<<24; for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { // 获得像素的颜色 int color = pix[w * i + j]; int red = ((color & 0x00FF0000) >> 16); int green = ((color & 0x0000FF00) >> 8); int blue = color & 0x000000FF; color = (red + green + blue)/3; color = alpha | (color << 16) | (color << 8) | color; pix[w * i + j] = color; } } Bitmap result=Bitmap.createBitmap(w, h, Config.RGB_565); result.setPixels(pix, 0, w, 0, 0,w, h); return result; }}
~~~
封装NDK函数的JAVA类LibFuns.java的源码如下:
~~~
package com.testToGray;public class LibFuns { static { System.loadLibrary("ImgToGray"); } /** * @param width the current view width * @param height the current view height */ public static native int[] ImgToGray(int[] buf, int w, int h);}
~~~
彩图转换为灰度图的ImgToGray.cpp源码:
~~~
#include <jni.h>#include <stdio.h>#include <stdlib.h>extern "C" {JNIEXPORT jintArray JNICALL Java_com_testToGray_LibFuns_ImgToGray( JNIEnv* env, jobject obj, jintArray buf, int w, int h);};JNIEXPORT jintArray JNICALL Java_com_testToGray_LibFuns_ImgToGray( JNIEnv* env, jobject obj, jintArray buf, int w, int h) { jint *cbuf; cbuf = env->GetIntArrayElements(buf, false); if (cbuf == NULL) { return 0; /* exception occurred */ } int alpha = 0xFF << 24; for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { // 获得像素的颜色 int color = cbuf[w * i + j]; int red = ((color & 0x00FF0000) >> 16); int green = ((color & 0x0000FF00) >> 8); int blue = color & 0x000000FF; color = (red + green + blue) / 3; color = alpha | (color << 16) | (color << 8) | color; cbuf[w * i + j] = color; } } int size=w * h; jintArray result = env->NewIntArray(size); env->SetIntArrayRegion(result, 0, size, cbuf); env->ReleaseIntArrayElements(buf, cbuf, 0); return result;}
~~~
Android.mk的源码:
~~~
LOCAL_PATH:= $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := ImgToGrayLOCAL_SRC_FILES := ImgToGray.cppinclude $(BUILD_SHARED_LIBRARY)
~~~
Android提高第十五篇之ListView自适应实现表格
最后更新于:2022-04-01 15:47:15
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
上次介绍了[使用GridView实现表格](http://blog.csdn.net/hellogv/archive/2010/11/18/6019301.aspx),这次就说说如何用ListView实现自适应的表格。GridView比ListView更容易实现自适应的表格,但是GridView每个格单元的大小固定,而ListView实现的表格可以自定义每个格单元的大小,但因此实现自适应表格也会复杂些(格单元大小不一)。另外,GridView实现的表格可以定位在具体某个格单元,而ListView实现的表格则只能定位在表格行。**因此还是那句老话:根据具体的使用环境而选择GridView 或者 ListView实现表格。**
先贴出本文程序运行的效果图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a1ce884.gif)
本文实现的ListView表格,可以每个格单元大小不一,文本(TextView)或图片(ImageView)做格单元的数据,**不需要预先定义XML实现样式(自适应的根本目标)**。由于ListView置于HorizontalScrollView中,因此对于列比较多/列数据比较长的数据表也能很好地适应其宽度。
main.xml源码如下:
~~~
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <HorizontalScrollView android:id="@+id/HorizontalScrollView01" android:layout_height="fill_parent" android:layout_width="fill_parent"> <ListView android:id="@+id/ListView01" android:layout_height="wrap_content" android:layout_width="wrap_content"></ListView> </HorizontalScrollView></LinearLayout>
~~~
主类testMyListView.java的源码如下:
~~~
package com.testMyListView;import java.util.ArrayList;import com.testMyListView.TableAdapter.TableCell;import com.testMyListView.TableAdapter.TableRow;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.widget.AdapterView;import android.widget.ListView;import android.widget.LinearLayout.LayoutParams;import android.widget.Toast;/** * @author hellogv */public class testMyListView extends Activity { /** Called when the activity is first created. */ ListView lv; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.setTitle("ListView自适应实现表格---hellogv"); lv = (ListView) this.findViewById(R.id.ListView01); ArrayList<TableRow> table = new ArrayList<TableRow>(); TableCell[] titles = new TableCell[5];// 每行5个单元 int width = this.getWindowManager().getDefaultDisplay().getWidth()/titles.length; // 定义标题 for (int i = 0; i < titles.length; i++) { titles[i] = new TableCell("标题" + String.valueOf(i), width + 8 * i, LayoutParams.FILL_PARENT, TableCell.STRING); } table.add(new TableRow(titles)); // 每行的数据 TableCell[] cells = new TableCell[5];// 每行5个单元 for (int i = 0; i < cells.length - 1; i++) { cells[i] = new TableCell("No." + String.valueOf(i), titles[i].width, LayoutParams.FILL_PARENT, TableCell.STRING); } cells[cells.length - 1] = new TableCell(R.drawable.icon, titles[cells.length - 1].width, LayoutParams.WRAP_CONTENT, TableCell.IMAGE); // 把表格的行添加到表格 for (int i = 0; i < 12; i++) table.add(new TableRow(cells)); TableAdapter tableAdapter = new TableAdapter(this, table); lv.setAdapter(tableAdapter); lv.setOnItemClickListener(new ItemClickEvent()); } class ItemClickEvent implements AdapterView.OnItemClickListener { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { Toast.makeText(testMyListView.this, "选中第"+String.valueOf(arg2)+"行", 500).show(); } }}
~~~
ListView自适应实现Table的类TableAdapter.java代码如下:
PS:TableCell是格单元的类,TableRow是表格行的类,TableRowView是实现表格行的组件。实现步骤:TableCell --> TableRow(TableRowView)-->ListView
~~~
package com.testMyListView;import java.util.List;import android.content.Context;import android.graphics.Color;import android.view.Gravity;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.TextView;public class TableAdapter extends BaseAdapter { private Context context; private List<TableRow> table; public TableAdapter(Context context, List<TableRow> table) { this.context = context; this.table = table; } @Override public int getCount() { return table.size(); } @Override public long getItemId(int position) { return position; } public TableRow getItem(int position) { return table.get(position); } public View getView(int position, View convertView, ViewGroup parent) { TableRow tableRow = table.get(position); return new TableRowView(this.context, tableRow); } /** * TableRowView 实现表格行的样式 * @author hellogv */ class TableRowView extends LinearLayout { public TableRowView(Context context, TableRow tableRow) { super(context); this.setOrientation(LinearLayout.HORIZONTAL); for (int i = 0; i < tableRow.getSize(); i++) {//逐个格单元添加到行 TableCell tableCell = tableRow.getCellValue(i); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( tableCell.width, tableCell.height);//按照格单元指定的大小设置空间 layoutParams.setMargins(0, 0, 1, 1);//预留空隙制造边框 if (tableCell.type == TableCell.STRING) {//如果格单元是文本内容 TextView textCell = new TextView(context); textCell.setLines(1); textCell.setGravity(Gravity.CENTER); textCell.setBackgroundColor(Color.BLACK);//背景黑色 textCell.setText(String.valueOf(tableCell.value)); addView(textCell, layoutParams); } else if (tableCell.type == TableCell.IMAGE) {//如果格单元是图像内容 ImageView imgCell = new ImageView(context); imgCell.setBackgroundColor(Color.BLACK);//背景黑色 imgCell.setImageResource((Integer) tableCell.value); addView(imgCell, layoutParams); } } this.setBackgroundColor(Color.WHITE);//背景白色,利用空隙来实现边框 } } /** * TableRow 实现表格的行 * @author hellogv */ static public class TableRow { private TableCell[] cell; public TableRow(TableCell[] cell) { this.cell = cell; } public int getSize() { return cell.length; } public TableCell getCellValue(int index) { if (index >= cell.length) return null; return cell[index]; } } /** * TableCell 实现表格的格单元 * @author hellogv */ static public class TableCell { static public final int STRING = 0; static public final int IMAGE = 1; public Object value; public int width; public int height; private int type; public TableCell(Object value, int width, int height, int type) { this.value = value; this.width = width; this.height = height; this.type = type; } }}
~~~
Android提高第十四篇之探秘TelephonyManager
最后更新于:2022-04-01 15:47:13
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
上次介绍了如何使用JAVA的反射机制来[调用蓝牙的隐藏API](http://blog.csdn.net/hellogv/archive/2010/11/29/6042091.aspx),这次继续练习JAVA的反射机制,探秘TelephonyManager在Framework里包含却在SDK隐藏的几项功能。先来看看本文程序运行的效果图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a191220.gif)
本文程序演示了以下功能:
1.所有来电自动接听;
2.所有来电自动挂断;
3.开启/关闭Radio;
4.开启/关闭数据连接(WAP or NET的连接)。
调用TelephonyManager的隐藏API是先参考Framework的/base/telephony/java/com/android/internal/telephony/ITelephony.aidl,然后自己实现一个ITelephony.aidl,最后在TelephonyManager中通过反射机制实例化自定义的ITelephony,实例化之后就可以调用ITelephony里面的函数了。
本文程序需要在AndroidManifest.xml添加以下两行代码,以获得权限:
<uses-permission android:name="android.permission.CALL_PHONE" /><uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
main.xml源码如下:
~~~
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <RadioGroup android:layout_height="wrap_content" android:layout_width="fill_parent" android:id="@+id/rGrpSelect"> <RadioButton android:layout_height="wrap_content" android:layout_width="fill_parent" android:id="@+id/rbtnAutoAccept" android:text="所有来电自动接听"></RadioButton> <RadioButton android:layout_height="wrap_content" android:layout_width="fill_parent" android:id="@+id/rbtnAutoReject" android:text="所有来电自动挂断"></RadioButton> </RadioGroup> <ToggleButton android:layout_height="wrap_content" android:layout_width="fill_parent" android:id="@+id/tbtnRadioSwitch" android:textOn="Radio已经启动" android:textOff="Radio已经关闭" android:textSize="24dip" android:textStyle="normal"></ToggleButton> <ToggleButton android:layout_height="wrap_content" android:layout_width="fill_parent" android:id="@+id/tbtnDataConn" android:textSize="24dip" android:textStyle="normal" android:textOn="允许数据连接" android:textOff="禁止数据连接"></ToggleButton></LinearLayout>
~~~
PhoneUtils.java是手机功能类,从TelephonyManager中实例化ITelephony并返回,源码如下:
~~~
package com.testTelephony;import java.lang.reflect.Field;import java.lang.reflect.Method;import com.android.internal.telephony.ITelephony;import android.telephony.TelephonyManager;import android.util.Log;public class PhoneUtils { /** * 从TelephonyManager中实例化ITelephony,并返回 */ static public ITelephony getITelephony(TelephonyManager telMgr) throws Exception { Method getITelephonyMethod = telMgr.getClass().getDeclaredMethod("getITelephony"); getITelephonyMethod.setAccessible(true);//私有化函数也能使用 return (ITelephony)getITelephonyMethod.invoke(telMgr); } static public void printAllInform(Class clsShow) { try { // 取得所有方法 Method[] hideMethod = clsShow.getDeclaredMethods(); int i = 0; for (; i < hideMethod.length; i++) { Log.e("method name", hideMethod[i].getName()); } // 取得所有常量 Field[] allFields = clsShow.getFields(); for (i = 0; i < allFields.length; i++) { Log.e("Field name", allFields[i].getName()); } } catch (SecurityException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (IllegalArgumentException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
~~~
testTelephony.java是主类,使用PhoneStateListener监听通话状态,以及实现上述4种电话控制功能,源码如下:
~~~
package com.testTelephony;import android.app.Activity;import android.os.Bundle;import android.telephony.PhoneStateListener;import android.telephony.TelephonyManager;import android.util.Log;import android.view.View;import android.widget.RadioGroup;import android.widget.ToggleButton;public class testTelephony extends Activity { /** Called when the activity is first created. */ RadioGroup rg;//来电操作单选框 ToggleButton tbtnRadioSwitch;//Radio开关 ToggleButton tbtnDataConn;//数据连接的开关 TelephonyManager telMgr; CallStateListener stateListner; int checkedId=0; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); telMgr= (TelephonyManager)getSystemService(TELEPHONY_SERVICE); telMgr.listen(new CallStateListener(), CallStateListener.LISTEN_CALL_STATE); PhoneUtils.printAllInform(TelephonyManager.class); rg = (RadioGroup)findViewById(R.id.rGrpSelect); rg.setOnCheckedChangeListener(new CheckEvent()); tbtnRadioSwitch=(ToggleButton)this.findViewById(R.id.tbtnRadioSwitch); tbtnRadioSwitch.setOnClickListener(new ClickEvent()); try { tbtnRadioSwitch.setChecked(PhoneUtils.getITelephony(telMgr).isRadioOn()); } catch (Exception e) { Log.e("error",e.getMessage()); } tbtnDataConn=(ToggleButton)this.findViewById(R.id.tbtnDataConn); tbtnDataConn.setOnClickListener(new ClickEvent()); try { tbtnDataConn.setChecked(PhoneUtils.getITelephony(telMgr).isDataConnectivityPossible()); } catch (Exception e) { Log.e("error",e.getMessage()); } } /** * 来电时的操作 * @author GV * */ public class CheckEvent implements RadioGroup.OnCheckedChangeListener{ @Override public void onCheckedChanged(RadioGroup group, int checkedId) { testTelephony.this.checkedId=checkedId; } } /** * Radio和数据连接的开关 * @author GV * */ public class ClickEvent implements View.OnClickListener{ @Override public void onClick(View v) { if (v == tbtnRadioSwitch) { try { PhoneUtils.getITelephony(telMgr).setRadio(tbtnRadioSwitch.isChecked()); } catch (Exception e) { Log.e("error", e.getMessage()); } } else if(v==tbtnDataConn){ try { if(tbtnDataConn.isChecked()) PhoneUtils.getITelephony(telMgr).enableDataConnectivity(); else if(!tbtnDataConn.isChecked()) PhoneUtils.getITelephony(telMgr).disableDataConnectivity(); } catch (Exception e) { Log.e("error", e.getMessage()); } } } } /** * 监视电话状态 * @author GV * */ public class CallStateListener extends PhoneStateListener { @Override public void onCallStateChanged(int state, String incomingNumber) { if(state==TelephonyManager.CALL_STATE_IDLE)//挂断 { Log.e("IDLE",incomingNumber); } else if(state==TelephonyManager.CALL_STATE_OFFHOOK)//接听 { Log.e("OFFHOOK",incomingNumber); } else if(state==TelephonyManager.CALL_STATE_RINGING)//来电 { if(testTelephony.this.checkedId==R.id.rbtnAutoAccept) { try { //需要<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> PhoneUtils.getITelephony(telMgr).silenceRinger();//静铃 PhoneUtils.getITelephony(telMgr).answerRingingCall();//自动接听 } catch (Exception e) { Log.e("error",e.getMessage()); } } else if(testTelephony.this.checkedId==R.id.rbtnAutoReject) { try { PhoneUtils.getITelephony(telMgr).endCall();//挂断 PhoneUtils.getITelephony(telMgr).cancelMissedCallsNotification();//取消未接显示 } catch (Exception e) { Log.e("error",e.getMessage()); } } } super.onCallStateChanged(state, incomingNumber); } }}
~~~
Android提高第十三篇之探秘蓝牙隐藏API
最后更新于:2022-04-01 15:47:10
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
上次讲解[Android的蓝牙基本用法](http://blog.csdn.net/hellogv/archive/2010/11/26/6036849.aspx),这次讲得深入些,探讨下蓝牙方面的隐藏API。用过Android系统设置(Setting)的人都知道蓝牙搜索之后可以**建立配对**和**解除配对**,但是这两项功能的函数没有在SDK中给出,那么如何去使用这两项功能呢?本文利用JAVA的反射机制去调用这两项功能对应的函数:createBond和removeBond,具体的发掘和实现步骤如下:
1.使用Git工具下载platform/packages/apps/Settings.git,在Setting源码中查找关于**建立配对**和**解除配对**的API,知道这两个API的宿主(BluetoothDevice);
2.使用反射机制对BluetoothDevice枚举其所有方法和常量,看看是否存在:
~~~
static public void printAllInform(Class clsShow) { try { // 取得所有方法 Method[] hideMethod = clsShow.getMethods(); int i = 0; for (; i < hideMethod.length; i++) { Log.e("method name", hideMethod[i].getName()); } // 取得所有常量 Field[] allFields = clsShow.getFields(); for (i = 0; i < allFields.length; i++) { Log.e("Field name", allFields[i].getName()); } } catch (SecurityException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (IllegalArgumentException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }
~~~
结果如下:
~~~
11-29 09:19:12.012: method name(452): cancelBondProcess
11-29 09:19:12.020: method name(452): cancelPairingUserInput
**11-29 09:19:12.020: method name(452): createBond
**11-29 09:19:12.020: method name(452): createInsecureRfcommSocket
11-29 09:19:12.027: method name(452): createRfcommSocket
11-29 09:19:12.027: method name(452): createRfcommSocketToServiceRecord
11-29 09:19:12.027: method name(452): createScoSocket
11-29 09:19:12.027: method name(452): describeContents
11-29 09:19:12.035: method name(452): equals
11-29 09:19:12.035: method name(452): fetchUuidsWithSdp
11-29 09:19:12.035: method name(452): getAddress
11-29 09:19:12.035: method name(452): getBluetoothClass
11-29 09:19:12.043: method name(452): getBondState
11-29 09:19:12.043: method name(452): getName
11-29 09:19:12.043: method name(452): getServiceChannel
11-29 09:19:12.043: method name(452): getTrustState
11-29 09:19:12.043: method name(452): getUuids
11-29 09:19:12.043: method name(452): hashCode
11-29 09:19:12.043: method name(452): isBluetoothDock
**11-29 09:19:12.043: method name(452): removeBond
**11-29 09:19:12.043: method name(452): setPairingConfirmation
11-29 09:19:12.043: method name(452): setPasskey
11-29 09:19:12.043: method name(452): setPin
11-29 09:19:12.043: method name(452): setTrust
11-29 09:19:12.043: method name(452): toString
11-29 09:19:12.043: method name(452): writeToParcel
11-29 09:19:12.043: method name(452): convertPinToBytes
11-29 09:19:12.043: method name(452): getClass
11-29 09:19:12.043: method name(452): notify
11-29 09:19:12.043: method name(452): notifyAll
11-29 09:19:12.043: method name(452): wait
11-29 09:19:12.051: method name(452): wait
11-29 09:19:12.051: method name(452): wait
~~~
3.如果枚举发现API存在(SDK却隐藏),则自己实现调用方法:
~~~
/** * 与设备配对 参考源码:platform/packages/apps/Settings.git * /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java */ static public boolean createBond(Class btClass,BluetoothDevice btDevice) throws Exception { Method createBondMethod = btClass.getMethod("createBond"); Boolean returnValue = (Boolean) createBondMethod.invoke(btDevice); return returnValue.booleanValue(); } /** * 与设备解除配对 参考源码:platform/packages/apps/Settings.git * /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java */ static public boolean removeBond(Class btClass,BluetoothDevice btDevice) throws Exception { Method removeBondMethod = btClass.getMethod("removeBond"); Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice); return returnValue.booleanValue(); }
~~~
PS:SDK之所以不给出隐藏的API肯定有其原因,也许是出于安全性或者是后续版本兼容性的考虑,因此不能保证隐藏API能在所有Android平台上很好地运行。。。
本文程序运行效果如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a11f8f9.gif)
main.xml源码如下:
~~~
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:id="@+id/LinearLayout01" android:layout_height="wrap_content" android:layout_width="fill_parent"> <Button android:layout_height="wrap_content" android:id="@+id/btnSearch" android:text="Search" android:layout_width="160dip"></Button> <Button android:layout_height="wrap_content" android:layout_width="160dip" android:text="Show" android:id="@+id/btnShow"></Button> </LinearLayout> <LinearLayout android:id="@+id/LinearLayout02" android:layout_width="wrap_content" android:layout_height="wrap_content"></LinearLayout> <ListView android:id="@+id/ListView01" android:layout_width="fill_parent" android:layout_height="fill_parent"> </ListView></LinearLayout>
~~~
工具类ClsUtils.java源码如下:
~~~
package com.testReflect;import java.lang.reflect.Field;import java.lang.reflect.Method;import android.bluetooth.BluetoothDevice;import android.util.Log;public class ClsUtils { /** * 与设备配对 参考源码:platform/packages/apps/Settings.git * /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java */ static public boolean createBond(Class btClass,BluetoothDevice btDevice) throws Exception { Method createBondMethod = btClass.getMethod("createBond"); Boolean returnValue = (Boolean) createBondMethod.invoke(btDevice); return returnValue.booleanValue(); } /** * 与设备解除配对 参考源码:platform/packages/apps/Settings.git * /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java */ static public boolean removeBond(Class btClass,BluetoothDevice btDevice) throws Exception { Method removeBondMethod = btClass.getMethod("removeBond"); Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice); return returnValue.booleanValue(); } /** * * @param clsShow */ static public void printAllInform(Class clsShow) { try { // 取得所有方法 Method[] hideMethod = clsShow.getMethods(); int i = 0; for (; i < hideMethod.length; i++) { Log.e("method name", hideMethod[i].getName()); } // 取得所有常量 Field[] allFields = clsShow.getFields(); for (i = 0; i < allFields.length; i++) { Log.e("Field name", allFields[i].getName()); } } catch (SecurityException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (IllegalArgumentException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }}
~~~
主程序testReflect.java的源码如下:
~~~
package com.testReflect;import java.util.ArrayList;import java.util.List;import android.app.Activity;import android.bluetooth.BluetoothAdapter;import android.bluetooth.BluetoothDevice;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.content.IntentFilter;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.AdapterView;import android.widget.ArrayAdapter;import android.widget.Button;import android.widget.ListView;import android.widget.Toast;public class testReflect extends Activity { Button btnSearch, btnShow; ListView lvBTDevices; ArrayAdapter<String> adtDevices; List<String> lstDevices = new ArrayList<String>(); BluetoothDevice btDevice; BluetoothAdapter btAdapt; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); btnSearch = (Button) this.findViewById(R.id.btnSearch); btnSearch.setOnClickListener(new ClickEvent()); btnShow = (Button) this.findViewById(R.id.btnShow); btnShow.setOnClickListener(new ClickEvent()); lvBTDevices = (ListView) this.findViewById(R.id.ListView01); adtDevices = new ArrayAdapter<String>(testReflect.this, android.R.layout.simple_list_item_1, lstDevices); lvBTDevices.setAdapter(adtDevices); lvBTDevices.setOnItemClickListener(new ItemClickEvent()); btAdapt = BluetoothAdapter.getDefaultAdapter();// 初始化本机蓝牙功能 if (btAdapt.getState() == BluetoothAdapter.STATE_OFF)// 开蓝牙 btAdapt.enable(); // 注册Receiver来获取蓝牙设备相关的结果 IntentFilter intent = new IntentFilter(); intent.addAction(BluetoothDevice.ACTION_FOUND); intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); registerReceiver(searchDevices, intent); } private BroadcastReceiver searchDevices = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Bundle b = intent.getExtras(); Object[] lstName = b.keySet().toArray(); // 显示所有收到的消息及其细节 for (int i = 0; i < lstName.length; i++) { String keyName = lstName[i].toString(); Log.e(keyName, String.valueOf(b.get(keyName))); } // 搜索设备时,取得设备的MAC地址 if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device.getBondState() == BluetoothDevice.BOND_NONE) { String str = "未配对|" + device.getName() + "|" + device.getAddress(); lstDevices.add(str); // 获取设备名称和mac地址 adtDevices.notifyDataSetChanged(); } } } }; class ItemClickEvent implements AdapterView.OnItemClickListener { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { btAdapt.cancelDiscovery(); String str = lstDevices.get(arg2); String[] values = str.split("//|"); String address=values[2]; btDevice = btAdapt.getRemoteDevice(address); try { if(values[0].equals("未配对")) { Toast.makeText(testReflect.this, "由未配对转为已配对", 500).show(); ClsUtils.createBond(btDevice.getClass(), btDevice); } else if(values[0].equals("已配对")) { Toast.makeText(testReflect.this, "由已配对转为未配对", 500).show(); ClsUtils.removeBond(btDevice.getClass(), btDevice); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * 按键处理 * @author GV * */ class ClickEvent implements View.OnClickListener { @Override public void onClick(View v) { if (v == btnSearch) {//搜索附近的蓝牙设备 lstDevices.clear(); Object[] lstDevice = btAdapt.getBondedDevices().toArray(); for (int i = 0; i < lstDevice.length; i++) { BluetoothDevice device=(BluetoothDevice)lstDevice[i]; String str = "已配对|" + device.getName() + "|" + device.getAddress(); lstDevices.add(str); // 获取设备名称和mac地址 adtDevices.notifyDataSetChanged(); } // 开始搜索 setTitle("本机蓝牙地址:" + btAdapt.getAddress()); btAdapt.startDiscovery(); } else if(v==btnShow){//显示BluetoothDevice的所有方法和常量,包括隐藏API ClsUtils.printAllInform(btDevice.getClass()); } } }}
~~~
Android提高第十二篇之蓝牙传感应用
最后更新于:2022-04-01 15:47:08
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
上次介绍了[Android利用麦克风采集并显示模拟信号](http://blog.csdn.net/hellogv/archive/2010/11/24/6032046.aspx),这种采集手段适用于无IO控制、单纯读取信号的情况。如果传感器本身需要包含控制电路(例如采集血氧信号需要红外和红外线交替发射),那么传感器本身就需要带一片主控IC,片内采集并输出数字信号了。Android手机如何在不改硬件电路的前提下与这类数字传感器交互呢?可选的通信方式就有USB和蓝牙,两种方式各有好处:USB方式可以给传感器供电,蓝牙方式要自备电源;USB接口标准不一,蓝牙普遍支持SPP协议。本文选择蓝牙方式做介绍,介绍Android的蓝牙API以及蓝牙客户端的用法。
在Android 2.0,官方终于发布了蓝牙API(2.0以下系统的非官方的蓝牙API可以参考这里:[http://code.google.com/p/android-bluetooth/](http://code.google.com/p/android-bluetooth/))。Android手机一般以客户端的角色主动连接SPP协议设备(接上蓝牙模块的数字传感器),连接流程是:
1.使用registerReceiver注册BroadcastReceiver来获取蓝牙状态、搜索设备等消息;
2.使用BlueAdatper的搜索;
3.在BroadcastReceiver的onReceive()里取得搜索所得的蓝牙设备信息(如名称,MAC,RSSI);
4.通过设备的MAC地址来建立一个BluetoothDevice对象;
5.由BluetoothDevice衍生出BluetoothSocket,准备SOCKET来读写设备;
6.通过BluetoothSocket的createRfcommSocketToServiceRecord()方法来选择连接的协议/服务,这里用的是SPP(UUID:00001101-0000-1000-8000-00805F9B34FB);
7.Connect之后(如果还没配对则系统自动提示),使用BluetoothSocket的getInputStream()和getOutputStream()来读写蓝牙设备。
先来看看本文程序运行的效果图,所选的SPP协议设备是一款单导联心电采集表:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a0c6cc8.gif)
本文的代码较多,可以到这里下载:[http://www.pudn.com/downloads305/sourcecode/comm/android/detail1359043.html](http://www.pudn.com/downloads305/sourcecode/comm/android/detail1359043.html)
本文程序包含两个Activity(testBlueTooth和WaveDiagram),testBlueTooth是搜索建立蓝牙连接。BluetoothAdapter、BluetoothDevice和BluetoothSocket的使用很简单,除了前三者提供的功能外,还可以通过给系统发送消息来控制、获取蓝牙信息,例如:
注册BroadcastReceiver:
IntentFilter intent = new IntentFilter(); intent.addAction(BluetoothDevice.ACTION_FOUND);// 用BroadcastReceiver来取得搜索结果 intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); intent.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); registerReceiver(searchDevices, intent);
在BroadcastReceiver的onReceive()枚举所有消息的内容:
String action = intent.getAction(); Bundle b = intent.getExtras(); Object[] lstName = b.keySet().toArray(); // 显示所有收到的消息及其细节 for (int i = 0; i < lstName.length; i++) { String keyName = lstName[i].toString(); Log.e(keyName, String.valueOf(b.get(keyName))); }
在DDMS里面可以看到BluetoothDevice.ACTION_FOUND返回的消息:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a0f3984.gif)
程序另外一个Activity~~~WaveDiagram用于读取蓝牙数据并绘制波形图,这里要注意一下JAVA的byte的取值范围是跟C/C++不一样的,Android接收到的byte数据要做"& 0xFF"处理,转为C/C++等值的数据。
Android提高第十一篇之模拟信号示波器
最后更新于:2022-04-01 15:47:06
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
上次简单地介绍了[AudioRecord和AudioTrack的使用](http://blog.csdn.net/hellogv/archive/2010/11/22/6026455.aspx),这次就结合[SurfaceView](http://blog.csdn.net/hellogv/archive/2010/11/03/5985090.aspx)实现一个Android版的手机模拟信号示波器(PS:以前也讲过[J2ME版的手机示波器](http://blog.csdn.net/hellogv/archive/2010/08/27/5842657.aspx))。最近物联网炒得很火,作为手机软件开发者,如何在不修改手机硬件电路的前提下实现与第三方传感器结合呢?麦克风就是一个很好的ADC接口,通过麦克风与第三方传感器结合,再在软件里对模拟信号做相应的处理,就可以提供更丰富的传感化应用。
先来看看本文程序运行的效果图(屏幕录像速度较慢,真机实际运行起来会更加流畅):
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a08c79d.gif)
本文程序使用8000hz的采样率,对X轴方向绘图的实时性要求较高,如果不降低X轴的分辨率,程序的实时性较差,因此程序对X轴数据缩小区间为8倍~16倍。由于采用16位采样,因此Y轴数据的高度相对于手机屏幕来说也偏大,程序也对Y轴数据做缩小,区间为1倍~10倍。在SurfaceView的OnTouchListener方法里加入了波形基线的位置调节,直接在SurfaceView控件上触摸即可控制整体波形偏上或偏下显示。
main.xml源码如下:
~~~
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:id="@+id/LinearLayout01" android:layout_height="wrap_content" android:layout_width="fill_parent" android:orientation="horizontal"> <Button android:layout_height="wrap_content" android:id="@+id/btnStart" android:text="开始" android:layout_width="80dip"></Button> <Button android:layout_height="wrap_content" android:text="停止" android:id="@+id/btnExit" android:layout_width="80dip"></Button> <ZoomControls android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/zctlX"></ZoomControls> <ZoomControls android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/zctlY"></ZoomControls> </LinearLayout> <SurfaceView android:id="@+id/SurfaceView01" android:layout_height="fill_parent" android:layout_width="fill_parent"></SurfaceView></LinearLayout>
~~~
ClsOscilloscope.java是实现示波器的类库,包含AudioRecord操作线程和SurfaceView绘图线程的实现,两个线程同步操作,代码如下:
~~~
package com.testOscilloscope;import java.util.ArrayList;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Rect;import android.media.AudioRecord;import android.view.SurfaceView;public class ClsOscilloscope { private ArrayList<short[]> inBuf = new ArrayList<short[]>(); private boolean isRecording = false;// 线程控制标记 /** * X轴缩小的比例 */ public int rateX = 4; /** * Y轴缩小的比例 */ public int rateY = 4; /** * Y轴基线 */ public int baseLine = 0; /** * 初始化 */ public void initOscilloscope(int rateX, int rateY, int baseLine) { this.rateX = rateX; this.rateY = rateY; this.baseLine = baseLine; } /** * 开始 * * @param recBufSize * AudioRecord的MinBufferSize */ public void Start(AudioRecord audioRecord, int recBufSize, SurfaceView sfv, Paint mPaint) { isRecording = true; new RecordThread(audioRecord, recBufSize).start();// 开始录制线程 new DrawThread(sfv, mPaint).start();// 开始绘制线程 } /** * 停止 */ public void Stop() { isRecording = false; inBuf.clear();// 清除 } /** * 负责从MIC保存数据到inBuf * * @author GV * */ class RecordThread extends Thread { private int recBufSize; private AudioRecord audioRecord; public RecordThread(AudioRecord audioRecord, int recBufSize) { this.audioRecord = audioRecord; this.recBufSize = recBufSize; } public void run() { try { short[] buffer = new short[recBufSize]; audioRecord.startRecording();// 开始录制 while (isRecording) { // 从MIC保存数据到缓冲区 int bufferReadResult = audioRecord.read(buffer, 0, recBufSize); short[] tmpBuf = new short[bufferReadResult / rateX]; for (int i = 0, ii = 0; i < tmpBuf.length; i++, ii = i * rateX) { tmpBuf[i] = buffer[ii]; } synchronized (inBuf) {// inBuf.add(tmpBuf);// 添加数据 } } audioRecord.stop(); } catch (Throwable t) { } } }; /** * 负责绘制inBuf中的数据 * * @author GV * */ class DrawThread extends Thread { private int oldX = 0;// 上次绘制的X坐标 private int oldY = 0;// 上次绘制的Y坐标 private SurfaceView sfv;// 画板 private int X_index = 0;// 当前画图所在屏幕X轴的坐标 private Paint mPaint;// 画笔 public DrawThread(SurfaceView sfv, Paint mPaint) { this.sfv = sfv; this.mPaint = mPaint; } public void run() { while (isRecording) { ArrayList<short[]> buf = new ArrayList<short[]>(); synchronized (inBuf) { if (inBuf.size() == 0) continue; buf = (ArrayList<short[]>) inBuf.clone();// 保存 inBuf.clear();// 清除 } for (int i = 0; i < buf.size(); i++) { short[] tmpBuf = buf.get(i); SimpleDraw(X_index, tmpBuf, rateY, baseLine);// 把缓冲区数据画出来 X_index = X_index + tmpBuf.length; if (X_index > sfv.getWidth()) { X_index = 0; } } } } /** * 绘制指定区域 * * @param start * X轴开始的位置(全屏) * @param buffer * 缓冲区 * @param rate * Y轴数据缩小的比例 * @param baseLine * Y轴基线 */ void SimpleDraw(int start, short[] buffer, int rate, int baseLine) { if (start == 0) oldX = 0; Canvas canvas = sfv.getHolder().lockCanvas( new Rect(start, 0, start + buffer.length, sfv.getHeight()));// 关键:获取画布 canvas.drawColor(Color.BLACK);// 清除背景 int y; for (int i = 0; i < buffer.length; i++) {// 有多少画多少 int x = i + start; y = buffer[i] / rate + baseLine;// 调节缩小比例,调节基准线 canvas.drawLine(oldX, oldY, x, y, mPaint); oldX = x; oldY = y; } sfv.getHolder().unlockCanvasAndPost(canvas);// 解锁画布,提交画好的图像 } }}
~~~
testOscilloscope.java是主程序,控制UI和ClsOscilloscope,代码如下:
~~~
package com.testOscilloscope;import android.app.Activity;import android.graphics.Color;import android.graphics.Paint;import android.media.AudioFormat;import android.media.AudioRecord;import android.media.MediaRecorder;import android.os.Bundle;import android.view.MotionEvent;import android.view.SurfaceView;import android.view.View;import android.view.View.OnTouchListener;import android.widget.Button;import android.widget.ZoomControls;public class testOscilloscope extends Activity { /** Called when the activity is first created. */ Button btnStart,btnExit; SurfaceView sfv; ZoomControls zctlX,zctlY; ClsOscilloscope clsOscilloscope=new ClsOscilloscope(); static final int frequency = 8000;//分辨率 static final int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO; static final int audioEncoding = AudioFormat.ENCODING_PCM_16BIT; static final int xMax = 16;//X轴缩小比例最大值,X轴数据量巨大,容易产生刷新延时 static final int xMin = 8;//X轴缩小比例最小值 static final int yMax = 10;//Y轴缩小比例最大值 static final int yMin = 1;//Y轴缩小比例最小值 int recBufSize;//录音最小buffer大小 AudioRecord audioRecord; Paint mPaint; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //录音组件 recBufSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding); audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, recBufSize); //按键 btnStart = (Button) this.findViewById(R.id.btnStart); btnStart.setOnClickListener(new ClickEvent()); btnExit = (Button) this.findViewById(R.id.btnExit); btnExit.setOnClickListener(new ClickEvent()); //画板和画笔 sfv = (SurfaceView) this.findViewById(R.id.SurfaceView01); sfv.setOnTouchListener(new TouchEvent()); mPaint = new Paint(); mPaint.setColor(Color.GREEN);// 画笔为绿色 mPaint.setStrokeWidth(1);// 设置画笔粗细 //示波器类库 clsOscilloscope.initOscilloscope(xMax/2, yMax/2, sfv.getHeight()/2); //缩放控件,X轴的数据缩小的比率高些 zctlX = (ZoomControls)this.findViewById(R.id.zctlX); zctlX.setOnZoomInClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(clsOscilloscope.rateX>xMin) clsOscilloscope.rateX--; setTitle("X轴缩小"+String.valueOf(clsOscilloscope.rateX)+"倍" +","+"Y轴缩小"+String.valueOf(clsOscilloscope.rateY)+"倍"); } }); zctlX.setOnZoomOutClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(clsOscilloscope.rateX<xMax) clsOscilloscope.rateX++; setTitle("X轴缩小"+String.valueOf(clsOscilloscope.rateX)+"倍" +","+"Y轴缩小"+String.valueOf(clsOscilloscope.rateY)+"倍"); } }); zctlY = (ZoomControls)this.findViewById(R.id.zctlY); zctlY.setOnZoomInClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(clsOscilloscope.rateY>yMin) clsOscilloscope.rateY--; setTitle("X轴缩小"+String.valueOf(clsOscilloscope.rateX)+"倍" +","+"Y轴缩小"+String.valueOf(clsOscilloscope.rateY)+"倍"); } }); zctlY.setOnZoomOutClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(clsOscilloscope.rateY<yMax) clsOscilloscope.rateY++; setTitle("X轴缩小"+String.valueOf(clsOscilloscope.rateX)+"倍" +","+"Y轴缩小"+String.valueOf(clsOscilloscope.rateY)+"倍"); } }); } @Override protected void onDestroy() { super.onDestroy(); android.os.Process.killProcess(android.os.Process.myPid()); } /** * 按键事件处理 * @author GV * */ class ClickEvent implements View.OnClickListener { @Override public void onClick(View v) { if (v == btnStart) { clsOscilloscope.baseLine=sfv.getHeight()/2; clsOscilloscope.Start(audioRecord,recBufSize,sfv,mPaint); } else if (v == btnExit) { clsOscilloscope.Stop(); } } } /** * 触摸屏动态设置波形图基线 * @author GV * */ class TouchEvent implements OnTouchListener{ @Override public boolean onTouch(View v, MotionEvent event) { clsOscilloscope.baseLine=(int)event.getY(); return true; } }}
~~~
Android提高第十篇之AudioRecord实现"助听器"
最后更新于:2022-04-01 15:47:03
本文来自[http://blog.csdn.net/hellogv/](http://blog.csdn.net/hellogv/) ,引用必须注明出处!
Android可以通过MediaRecorder和AudioRecord这两个工具来实现录音,MediaRecorder直接把麦克风的数据存到文件,并且能够直接进行编码(如AMR,MP3等),而AudioRecord则是读取麦克风的音频流。本文使用AudioRecord读取音频流,使用AudioTrack播放音频流,通过“边读边播放”以及增大音量的方式来实现一个简单的助听器程序。
PS:由于目前的Android模拟器还不支持AudioRecord,因此本程序需要编译之后放到真机运行。
先贴出本文程序运行截图:
PS:程序音量调节只是程序内部调节音量而已,要调到最大音量还需要手动设置系统音量。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-24_576cb0a06a11f.gif)
使用AudioRecord必须要申请许可,在AndroidManifest.xml里面添加这句:
<uses-permission android:name="android.permission.RECORD_AUDIO"></uses-permission>
main.xml的源码如下:
~~~
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Button android:layout_height="wrap_content" android:id="@+id/btnRecord" android:layout_width="fill_parent" android:text="开始边录边放"></Button> <Button android:layout_height="wrap_content" android:layout_width="fill_parent" android:text="停止" android:id="@+id/btnStop"></Button> <Button android:layout_height="wrap_content" android:id="@+id/btnExit" android:layout_width="fill_parent" android:text="退出"></Button> <TextView android:id="@+id/TextView01" android:layout_height="wrap_content" android:text="程序音量调节" android:layout_width="fill_parent"></TextView> <SeekBar android:layout_height="wrap_content" android:id="@+id/skbVolume" android:layout_width="fill_parent"></SeekBar></LinearLayout>
~~~
testRecord.java的源码如下:
~~~
package com.testRecord;import android.app.Activity;import android.media.AudioFormat;import android.media.AudioManager;import android.media.AudioRecord;import android.media.AudioTrack;import android.media.MediaRecorder;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.SeekBar;import android.widget.Toast;public class testRecord extends Activity { /** Called when the activity is first created. */ Button btnRecord, btnStop, btnExit; SeekBar skbVolume;//调节音量 boolean isRecording = false;//是否录放的标记 static final int frequency = 44100; static final int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO; static final int audioEncoding = AudioFormat.ENCODING_PCM_16BIT; int recBufSize,playBufSize; AudioRecord audioRecord; AudioTrack audioTrack; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); setTitle("助听器"); recBufSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding); playBufSize=AudioTrack.getMinBufferSize(frequency, channelConfiguration, audioEncoding); // ----------------------------------------- audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, recBufSize); audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, frequency, channelConfiguration, audioEncoding, playBufSize, AudioTrack.MODE_STREAM); //------------------------------------------ btnRecord = (Button) this.findViewById(R.id.btnRecord); btnRecord.setOnClickListener(new ClickEvent()); btnStop = (Button) this.findViewById(R.id.btnStop); btnStop.setOnClickListener(new ClickEvent()); btnExit = (Button) this.findViewById(R.id.btnExit); btnExit.setOnClickListener(new ClickEvent()); skbVolume=(SeekBar)this.findViewById(R.id.skbVolume); skbVolume.setMax(100);//音量调节的极限 skbVolume.setProgress(70);//设置seekbar的位置值 audioTrack.setStereoVolume(0.7f, 0.7f);//设置当前音量大小 skbVolume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { float vol=(float)(seekBar.getProgress())/(float)(seekBar.getMax()); audioTrack.setStereoVolume(vol, vol);//设置音量 } @Override public void onStartTrackingTouch(SeekBar seekBar) { // TODO Auto-generated method stub } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { // TODO Auto-generated method stub } }); } @Override protected void onDestroy() { super.onDestroy(); android.os.Process.killProcess(android.os.Process.myPid()); } class ClickEvent implements View.OnClickListener { @Override public void onClick(View v) { if (v == btnRecord) { isRecording = true; new RecordPlayThread().start();// 开一条线程边录边放 } else if (v == btnStop) { isRecording = false; } else if (v == btnExit) { isRecording = false; testRecord.this.finish(); } } } class RecordPlayThread extends Thread { public void run() { try { byte[] buffer = new byte[recBufSize]; audioRecord.startRecording();//开始录制 audioTrack.play();//开始播放 while (isRecording) { //从MIC保存数据到缓冲区 int bufferReadResult = audioRecord.read(buffer, 0, recBufSize); byte[] tmpBuf = new byte[bufferReadResult]; System.arraycopy(buffer, 0, tmpBuf, 0, bufferReadResult); //写入数据即播放 audioTrack.write(tmpBuf, 0, tmpBuf.length); } audioTrack.stop(); audioRecord.stop(); } catch (Throwable t) { Toast.makeText(testRecord.this, t.getMessage(), 1000); } } };}
~~~