Android游戏开发之九:VideoView类实例讲解

移动开发 Android 游戏开发
之前介绍了SurfaceView类的应用实例,本文来讲讲Android游戏开发中的VideoView类实例。

本节使用系统的示例类VideoView继续SurfaceView类相关内容的讲解,以让大家能更深入理解Android系统中图形绘制基础类的实现原理。也许你会发现无法改变VideoView类的控制方面,我们可以通过重构VideoView类来实现更加个性化的播放器。

下面是VideoView类的相关代码。

  1.     public class VideoView extends SurfaceView implements MediaPlayerControl {    
  2.         private String TAG = "VideoView";    
  3.         // settable by the client    
  4.         private Uri         mUri;    
  5.         private int         mDuration;    
  6.         // all possible internal states    
  7.         private static final int STATE_ERROR              = -1;    
  8.         private static final int STATE_IDLE               = 0;    
  9.         private static final int STATE_PREPARING          = 1;    
  10.         private static final int STATE_PREPARED           = 2;    
  11.         private static final int STATE_PLAYING            = 3;    
  12.         private static final int STATE_PAUSED             = 4;    
  13.         private static final int STATE_PLAYBACK_COMPLETED = 5;    
  14.         // mCurrentState is a VideoView object's current state.    
  15.         // mTargetState is the state that a method caller intends to reach.    
  16.         // For instance, regardless the VideoView object's current state,    
  17.         // calling pause() intends to bring the object to a target state    
  18.         // of STATE_PAUSED.    
  19.         private int mCurrentState = STATE_IDLE;    
  20.         private int mTargetState  = STATE_IDLE;    
  21.         // All the stuff we need for playing and showing a video    
  22.         private SurfaceHolder mSurfaceHolder = null;    
  23.         private MediaPlayer mMediaPlayer = null;    
  24.         private int         mVideoWidth;    
  25.         private int         mVideoHeight;    
  26.         private int         mSurfaceWidth;    
  27.         private int         mSurfaceHeight;    
  28.         private MediaController mMediaController;    
  29.         private OnCompletionListener mOnCompletionListener;    
  30.         private MediaPlayer.OnPreparedListener mOnPreparedListener;    
  31.         private int         mCurrentBufferPercentage;    
  32.         private OnErrorListener mOnErrorListener;    
  33.         private int         mSeekWhenPrepared;  // recording the seek position while preparing    
  34.         private boolean     mCanPause;    
  35.         private boolean     mCanSeekBack;    
  36.         private boolean     mCanSeekForward;    
  37.         public VideoView(Context context) {    
  38.             super(context);    
  39.             initVideoView();    
  40.         }    
  41.         public VideoView(Context context, AttributeSet attrs) {    
  42.             this(context, attrs, 0);    
  43.             initVideoView();    
  44.         }    
  45.         public VideoView(Context context, AttributeSet attrs, int defStyle) {    
  46.             super(context, attrs, defStyle);    
  47.             initVideoView();    
  48.         }    
  49.         @Override   
  50.         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    
  51.             //Log.i("@@@@", "onMeasure");    
  52.             int width = getDefaultSize(mVideoWidth, widthMeasureSpec);    
  53.             int height = getDefaultSize(mVideoHeight, heightMeasureSpec);    
  54.             if (mVideoWidth > 0 && mVideoHeight > 0) {    
  55.                 if ( mVideoWidth * height  > width * mVideoHeight ) {    
  56.                     //Log.i("@@@", "image too tall, correcting");    
  57.                     height = width * mVideoHeight / mVideoWidth;    
  58.                 } else if ( mVideoWidth * height  < width * mVideoHeight ) {    
  59.                     //Log.i("@@@", "image too wide, correcting");    
  60.                     width = height * mVideoWidth / mVideoHeight;    
  61.                 } else {    
  62.                     //Log.i("@@@", "aspect ratio is correct: " +    
  63.                             //width+"/"+height+"="+    
  64.                             //mVideoWidth+"/"+mVideoHeight);    
  65.                 }    
  66.             }    
  67.             //Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);    
  68.             setMeasuredDimension(width, height);    
  69.         }    
  70.         public int resolveAdjustedSize(int desiredSize, int measureSpec) {    
  71.             int result = desiredSize;    
  72.             int specMode = MeasureSpec.getMode(measureSpec);    
  73.             int specSize =  MeasureSpec.getSize(measureSpec);    
  74.             switch (specMode) {    
  75.                 case MeasureSpec.UNSPECIFIED:    
  76.                     /* Parent says we can be as big as we want. Just don't be larger   
  77.                      * than max size imposed on ourselves.   
  78.                      */   
  79.                     result = desiredSize;    
  80.                     break;    
  81.                 case MeasureSpec.AT_MOST:    
  82.                     /* Parent says we can be as big as we want, up to specSize.    
  83.                      * Don't be larger than specSize, and don't be larger than    
  84.                      * the max size imposed on ourselves.   
  85.                      */   
  86.                     result = Math.min(desiredSize, specSize);    
  87.                     break;    
  88.                 case MeasureSpec.EXACTLY:    
  89.                     // No choice. Do what we are told.    
  90.                     result = specSize;    
  91.                     break;    
  92.             }    
  93.             return result;    
  94.     }    
  95.         private void initVideoView() {    
  96.             mVideoWidth = 0;    
  97.             mVideoHeight = 0;    
  98.             getHolder().addCallback(mSHCallback);    
  99. getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);    
  100.             setFocusable(true);    
  101.             setFocusableInTouchMode(true);    
  102.             requestFocus();    
  103.             mCurrentState = STATE_IDLE;    
  104.             mTargetState  = STATE_IDLE;    
  105.         }    
  106.         public void setVideoPath(String path) {    
  107.             setVideoURI(Uri.parse(path));    
  108.         }    
  109.         public void setVideoURI(Uri uri) {    
  110.             mUri = uri;    
  111.             mSeekWhenPrepared = 0;    
  112.             openVideo();    
  113.             requestLayout();    
  114.             invalidate();    
  115.         }    
  116.         public void stopPlayback() {    
  117.             if (mMediaPlayer != null) {    
  118.                 mMediaPlayer.stop();    
  119.                 mMediaPlayer.release();    
  120.                 mMediaPlayer = null;    
  121.                 mCurrentState = STATE_IDLE;    
  122.                 mTargetState  = STATE_IDLE;    
  123.             }    
  124.         }    
  125.         private void openVideo() {    
  126.             if (mUri == null || mSurfaceHolder == null) {    
  127.                 // not ready for playback just yet, will try again later    
  128.                 return;    
  129.             }    
  130.             // Tell the music playback service to pause     
  131.             // TODO: these constants need to be published somewhere in the framework.    
  132.             Intent i = new Intent("com.android.music.musicservicecommand");    
  133.             i.putExtra("command", "pause");    
  134.             mContext.sendBroadcast(i);    
  135.             // we shouldn't clear the target state, because somebody might have    
  136.             // called start() previously    
  137.             release(false);    
  138.             try {    
  139.                 mMediaPlayer = new MediaPlayer();    
  140. mMediaPlayer.setOnPreparedListener(mPreparedListener);    
  141. mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);  
  142.                 mDuration = -1;    
  143. mMediaPlayer.setOnCompletionListener(mCompletionListener);    
  144. mMediaPlayer.setOnErrorListener(mErrorListener);    
  145. mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);    
  146.                 mCurrentBufferPercentage = 0;    
  147.                 mMediaPlayer.setDataSource(mContext, mUri);    
  148.                 mMediaPlayer.setDisplay(mSurfaceHolder);   mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);    
  149.                 mMediaPlayer.setScreenOnWhilePlaying(true);    
  150.                 mMediaPlayer.prepareAsync();    
  151.                 // we don't set the target state here either, but preserve the    
  152.                 // target state that was there before.    
  153.                 mCurrentState = STATE_PREPARING;    
  154.                 attachMediaController();    
  155.             } catch (IOException ex) {    
  156.                 Log.w(TAG, "Unable to open content: " + mUri, ex);    
  157.                 mCurrentState = STATE_ERROR;    
  158.                 mTargetState = STATE_ERROR;    
  159.                 mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);    
  160.                 return;    
  161.             } catch (IllegalArgumentException ex) {    
  162.                 Log.w(TAG, "Unable to open content: " + mUri, ex);    
  163.                 mCurrentState = STATE_ERROR;    
  164.                 mTargetState = STATE_ERROR;    
  165.                 mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);    
  166.                 return;    
  167.             }    
  168.         }    
  169.         public void setMediaController(MediaController controller) {    
  170.             if (mMediaController != null) {    
  171.                 mMediaController.hide();    
  172.             }    
  173.             mMediaController = controller;    
  174.             attachMediaController();    
  175.         }    
  176.         private void attachMediaController() {    
  177.             if (mMediaPlayer != null && mMediaController != null) {    
  178.                 mMediaController.setMediaPlayer(this);    
  179.                 View anchorView = this.getParent() instanceof View ?    
  180.                         (View)this.getParent() : this;    
  181.                 mMediaController.setAnchorView(anchorView);   mMediaController.setEnabled(isInPlaybackState());    
  182.             }    
  183.         }    
  184.         MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =    
  185.             new MediaPlayer.OnVideoSizeChangedListener() {    
  186.                 public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {    
  187.                     mVideoWidth = mp.getVideoWidth();    
  188.                     mVideoHeight = mp.getVideoHeight();    
  189.                     if (mVideoWidth != 0 && mVideoHeight != 0) {    
  190.                         getHolder().setFixedSize(mVideoWidth, mVideoHeight);    
  191.                     }    
  192.                 }    
  193.         };    
  194.         MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {    
  195.             public void onPrepared(MediaPlayer mp) {    
  196.                 mCurrentState = STATE_PREPARED;    
  197.                 // Get the capabilities of the player for this stream    
  198.                 Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,    
  199.                                           MediaPlayer.BYPASS_METADATA_FILTER);    
  200.                 if (data != null) {    
  201.                     mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)    
  202.                             || data.getBoolean(Metadata.PAUSE_AVAILABLE);    
  203.                     mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)    
  204.                             || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);    
  205.                     mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)    
  206.                             || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);    
  207.                 } else {    
  208.                     mCanPause = mCanSeekForward = mCanSeekForward = true;    
  209.                 }    
  210.                 if (mOnPreparedListener != null) {    
  211.                     mOnPreparedListener.onPrepared(mMediaPlayer);    
  212.                 }    
  213.                 if (mMediaController != null) {    
  214.                     mMediaController.setEnabled(true);    
  215.                 }    
  216.                 mVideoWidth = mp.getVideoWidth();    
  217.                 mVideoHeight = mp.getVideoHeight();    
  218.                 int seekToPosition = mSeekWhenPrepared;  // mSeekWhenPrepared may be changed after seekTo() call    
  219.                 if (seekToPosition != 0) {    
  220.                     seekTo(seekToPosition);    
  221.                 }    
  222.                 if (mVideoWidth != 0 && mVideoHeight != 0) {    
  223.                     //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);    
  224.                     getHolder().setFixedSize(mVideoWidth, mVideoHeight);    
  225.                     if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {    
  226.                         // We didn't actually change the size (it was already at the size    
  227.                         // we need), so we won't get a "surface changed" callback, so    
  228.                         // start the video here instead of in the callback.    
  229.                         if (mTargetState == STATE_PLAYING) {    
  230.                             start();    
  231.                             if (mMediaController != null) {    
  232.                                 mMediaController.show();    
  233.                             }    
  234.                         } else if (!isPlaying() &&    
  235.                                    (seekToPosition != 0 || getCurrentPosition() > 0)) {    
  236.                            if (mMediaController != null) {    
  237.                                // Show the media controls when we're paused into a video and make 'em stick.    
  238.                                mMediaController.show(0);    
  239.                            }    
  240.                        }    
  241.                     }    
  242.                 } else {    
  243.                     // We don't know the video size yet, but should start anyway.    
  244.                     // The video size might be reported to us later.    
  245.                     if (mTargetState == STATE_PLAYING) {    
  246.                         start();    
  247.                     }    
  248.                 }    
  249.             }    
  250.         };    
  251.         private MediaPlayer.OnCompletionListener mCompletionListener =    
  252.             new MediaPlayer.OnCompletionListener() {    
  253.             public void onCompletion(MediaPlayer mp) {    
  254.                 mCurrentState = STATE_PLAYBACK_COMPLETED;    
  255.                 mTargetState = STATE_PLAYBACK_COMPLETED;    
  256.                 if (mMediaController != null) {    
  257.                     mMediaController.hide();    
  258.                 }    
  259.                 if (mOnCompletionListener != null) {   mOnCompletionListener.onCompletion(mMediaPlayer);    
  260.                 }    
  261.             }    
  262.         };    
  263.         private MediaPlayer.OnErrorListener mErrorListener =    
  264.             new MediaPlayer.OnErrorListener() {    
  265.             public boolean onError(MediaPlayer mp, int framework_err, int impl_err) {    
  266.                 Log.d(TAG, "Error: " + framework_err + "," + impl_err);    
  267.                 mCurrentState = STATE_ERROR;    
  268.                 mTargetState = STATE_ERROR;    
  269.                 if (mMediaController != null) {    
  270.                     mMediaController.hide();    
  271.                 }    
  272.                 /* If an error handler has been supplied, use it and finish. */   
  273.                 if (mOnErrorListener != null) {    
  274.                     if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) {    
  275.                         return true;    
  276.                     }    
  277.                 }    
  278.                 /* Otherwise, pop up an error dialog so the user knows that   
  279.                  * something bad has happened. Only try and pop up the dialog   
  280.                  * if we're attached to a window. When we're going away and no   
  281.                  * longer have a window, don't bother showing the user an error.   
  282.                  */   
  283.                 if (getWindowToken() != null) {    
  284.                     Resources r = mContext.getResources();    
  285.                     int messageId;    
  286.                     if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {    
  287.                         messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback;    
  288.                     } else {    
  289.                         messageId = com.android.internal.R.string.VideoView_error_text_unknown;    
  290.                     }    
  291.                     new AlertDialog.Builder(mContext)    
  292. .setTitle(com.android.internal.R.string.VideoView_error_title)    
  293.                             .setMessage(messageId)    
  294.                             .setPositiveButton(com.android.internal.R.string.VideoView_error_button,    
  295.                                     new DialogInterface.OnClickListener() {    
  296.                                         public void onClick(DialogInterface dialog, int whichButton) {    
  297.                                             /* If we get here, there is no onError listener, so   
  298.                                              * at least inform them that the video is over.   
  299.                                              */   
  300.                                             if (mOnCompletionListener != null) {    
  301. mOnCompletionListener.onCompletion(mMediaPlayer);    
  302.                                             }    
  303.                                         }    
  304.                                     })    
  305.                             .setCancelable(false)    
  306.                             .show();    
  307.                 }    
  308.                 return true;    
  309.             }    
  310.         };    
  311.         private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =    
  312.             new MediaPlayer.OnBufferingUpdateListener() {    
  313.             public void onBufferingUpdate(MediaPlayer mp, int percent) {    
  314.                 mCurrentBufferPercentage = percent;    
  315.             }    
  316.         };    
  317.         /**   
  318.          * Register a callback to be invoked when the media file   
  319.          * is loaded and ready to go.   
  320.          *   
  321.          * @param l The callback that will be run   
  322.          */   
  323.         public void setOnPreparedListener(MediaPlayer.OnPreparedListener l)    
  324.         {    
  325.             mOnPreparedListener = l;    
  326.         }    
  327.         /**   
  328.          * Register a callback to be invoked when the end of a media file   
  329.          * has been reached during playback.   
  330.          *   
  331.          * @param l The callback that will be run   
  332.          */   
  333.         public void setOnCompletionListener(OnCompletionListener l)    
  334.         {    
  335.             mOnCompletionListener = l;    
  336.         }    
  337.         /**   
  338.          * Register a callback to be invoked when an error occurs   
  339.          * during playback or setup.  If no listener is specified,   
  340.          * or if the listener returned false, VideoView will inform   
  341.          * the user of any errors.   
  342.          *   
  343.          * @param l The callback that will be run   
  344.          */   
  345.         public void setOnErrorListener(OnErrorListener l)    
  346.         {    
  347.             mOnErrorListener = l;    
  348.         }    
  349.         SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()    
  350.         {    
  351.             public void surfaceChanged(SurfaceHolder holder, int format,    
  352.                                         int w, int h)    
  353.             {    
  354.                 mSurfaceWidth = w;    
  355.                 mSurfaceHeight = h;    
  356.                 boolean isValidState =  (mTargetState == STATE_PLAYING);    
  357.                 boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h);    
  358.                 if (mMediaPlayer != null && isValidState && hasValidSize) {    
  359.                     if (mSeekWhenPrepared != 0) {    
  360.                         seekTo(mSeekWhenPrepared);    
  361.                     }    
  362.                     start();    
  363.                     if (mMediaController != null) {    
  364.                         mMediaController.show();    
  365.                     }    
  366.                 }    
  367.             }    
  368.             public void surfaceCreated(SurfaceHolder holder)    
  369.             {    
  370.                 mSurfaceHolder = holder;    
  371.                 openVideo();    
  372.             }    
  373.             public void surfaceDestroyed(SurfaceHolder holder)    
  374.             {    
  375.                 // after we return from this we can't use the surface any more    
  376.                 mSurfaceHolder = null;    
  377.                 if (mMediaController != null) mMediaController.hide();    
  378.                 release(true);    
  379.             }    
  380.         };    
  381.         /*   
  382.          * release the media player in any state   
  383.          */   
  384.         private void release(boolean cleartargetstate) {    
  385.             if (mMediaPlayer != null) {    
  386.                 mMediaPlayer.reset();    
  387.                 mMediaPlayer.release();    
  388.                 mMediaPlayer = null;    
  389.                 mCurrentState = STATE_IDLE;    
  390.                 if (cleartargetstate) {    
  391.                     mTargetState  = STATE_IDLE;    
  392.                 }    
  393.             }    
  394.         }    
  395.         @Override   
  396.         public boolean onTouchEvent(MotionEvent ev) {    
  397.             if (isInPlaybackState() && mMediaController != null) {    
  398.                 toggleMediaControlsVisiblity();    
  399.             }    
  400.             return false;    
  401.         }    
  402.         @Override   
  403.         public boolean onTrackballEvent(MotionEvent ev) {    
  404.             if (isInPlaybackState() && mMediaController != null) {    
  405.                 toggleMediaControlsVisiblity();    
  406.             }    
  407.             return false;    
  408.         }    
  409.         @Override   
  410.         public boolean onKeyDown(int keyCode, KeyEvent event)    
  411.         {    
  412.             boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&    
  413.                                          keyCode != KeyEvent.KEYCODE_VOLUME_UP &&    
  414.                                          keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&    
  415.                                          keyCode != KeyEvent.KEYCODE_MENU &&    
  416.                                          keyCode != KeyEvent.KEYCODE_CALL &&    
  417.                                          keyCode != KeyEvent.KEYCODE_ENDCALL;    
  418.             if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {    
  419.                 if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||    
  420.                         keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {    
  421.                     if (mMediaPlayer.isPlaying()) {    
  422.                         pause();    
  423.                         mMediaController.show();    
  424.                     } else {    
  425.                         start();    
  426.                         mMediaController.hide();    
  427.                     }    
  428.                     return true;    
  429.                 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP     
  430.                         && mMediaPlayer.isPlaying()) {    
  431.                     pause();    
  432.                     mMediaController.show();    
  433.                 } else {    
  434.                     toggleMediaControlsVisiblity();    
  435.                 }    
  436.             }    
  437.             return super.onKeyDown(keyCode, event);    
  438.         }    
  439.         private void toggleMediaControlsVisiblity() {    
  440.             if (mMediaController.isShowing()) {     
  441.                 mMediaController.hide();    
  442.             } else {    
  443.                 mMediaController.show();    
  444.             }    
  445.         }    
  446.         public void start() {    
  447.             if (isInPlaybackState()) {    
  448.                 mMediaPlayer.start();    
  449.                 mCurrentState = STATE_PLAYING;    
  450.             }    
  451.             mTargetState = STATE_PLAYING;    
  452.         }    
  453.         public void pause() {    
  454.             if (isInPlaybackState()) {    
  455.                 if (mMediaPlayer.isPlaying()) {    
  456.                     mMediaPlayer.pause();    
  457.                     mCurrentState = STATE_PAUSED;    
  458.                 }    
  459.             }    
  460.             mTargetState = STATE_PAUSED;    
  461.         }    
  462.         // cache duration as mDuration for faster access    
  463.         public int getDuration() {    
  464.             if (isInPlaybackState()) {    
  465.                 if (mDuration > 0) {    
  466.                     return mDuration;    
  467.                 }    
  468.                 mDuration = mMediaPlayer.getDuration();    
  469.                 return mDuration;    
  470.             }    
  471.             mDuration = -1;    
  472.             return mDuration;    
  473.         }    
  474.         public int getCurrentPosition() {    
  475.             if (isInPlaybackState()) {    
  476.                 return mMediaPlayer.getCurrentPosition();    
  477.             }    
  478.             return 0;    
  479.         }    
  480.         public void seekTo(int msec) {    
  481.             if (isInPlaybackState()) {    
  482.                 mMediaPlayer.seekTo(msec);    
  483.                 mSeekWhenPrepared = 0;    
  484.             } else {    
  485.                 mSeekWhenPrepared = msec;    
  486.             }    
  487.         }        
  488.         public boolean isPlaying() {    
  489.             return isInPlaybackState() && mMediaPlayer.isPlaying();    
  490.         }    
  491.         public int getBufferPercentage() {    
  492.             if (mMediaPlayer != null) {    
  493.                 return mCurrentBufferPercentage;    
  494.             }    
  495.             return 0;    
  496.         }    
  497.         private boolean isInPlaybackState() {    
  498.             return (mMediaPlayer != null &&    
  499.                     mCurrentState != STATE_ERROR &&    
  500.                     mCurrentState != STATE_IDLE &&    
  501.                     mCurrentState != STATE_PREPARING);    
  502.         }    
  503.         public boolean canPause() {    
  504.             return mCanPause;    
  505.         }    
  506.         public boolean canSeekBackward() {    
  507.             return mCanSeekBack;    
  508.         }    
  509.         public boolean canSeekForward() {    
  510.             return mCanSeekForward;    
  511.         }    
  512.     }   

 

责任编辑:闫佳明 来源: jizhuomi
相关推荐

2013-05-20 17:51:47

Android游戏开发SurfaceView

2013-05-21 11:26:49

Android游戏开发Sensor感应

2011-08-22 13:46:15

iPhone开发GameKit 蓝牙

2013-05-20 17:21:34

2013-05-20 15:42:22

2013-05-20 17:13:17

Android游戏开发CanvasPaint

2013-05-21 14:10:11

Android游戏开发SoundPool类同时多音效

2009-06-11 09:19:38

netbeans实例J2ME游戏

2011-04-12 08:40:23

IMFAndroid

2013-05-20 17:04:09

2013-08-01 14:03:49

JavaScript

2010-07-08 15:17:24

2013-01-10 14:54:48

Android开发组件Intent

2013-05-20 17:07:26

2009-12-10 15:09:46

PHP搜索引擎类

2011-05-25 15:49:32

HP墨盒讲解

2013-05-21 13:55:51

Android游戏开发图像渐变特效

2013-05-20 17:48:20

2013-05-21 10:42:48

Android游戏开发Bitmap位图旋转

2013-05-21 11:24:07

Android游戏开发Sensor重力感应
点赞
收藏

51CTO技术栈公众号