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提高第十九篇之&quot;多方向&quot;抽屉

最后更新于: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实现&quot;助听器&quot;

最后更新于: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); } } };} ~~~
';