首页 文章 精选 留言 我的

精选列表

搜索[网站开发],共10000篇文章
优秀的个人博客,低调大师

Android应用开发提高系列(2)——《Practical Java 中文版》读书笔记(下)

正文 注意:条目和用语可能与书籍有所出入,但尽量保持原样加一些自己的理解。 一、性能 1. 先把焦点放在设计、数据结构和算法身上 备注:良好的设计、明智的选择数据结构和算法可能比高效代码更重要。 2. 不要依赖编译器优化技术 3. 理解运行时(runtime)代码优化 备注:JIT将bytecode于运行时转换为本地二进制码,从而提高性能。因此编译后代码被执行次数越多,本机代码生成代价就很合算。 4. 连接字符串使用StringBuffer要比String快,尤其是大量字符串拼接 5. 将对象创建成本降至最小 备注:复用既有对象,不要创建非必要的对象,只在需要的时候才创建它们。 6. 将同步化(synchronization)降至最低 备注:如果synchronized函数抛出异常,则在异常离开函数之前,锁会自动释放。如果整个函数都需要被同步化,为了产生体积较小且执行速度较快的代码,请优先使用函数修饰符,而不是在函数内使用synchronized代码块。 7. 尽可能使用stack变量 备注:如果在函数中频繁访问成员变量、静态变量,可以用本地(local)变量替代,最后操作完后再赋值给成员/静态变量。 8. 尽可能的使用static、final和private函数 备注:此类函数可以在编译期间被静态决议(statically resolved),而不需要动态议决(dynamic resolved)。(子类无法覆写) 9. 类的成员变量、静态变量都有缺省值,务须重复初始化 备注:记住,本地变量没有缺省值(例如函数内定义的变量)。 10. 尽可能的使用基本数据类型 备注:如int、short、char、boolean,使得代码更快更小。 11. 不要使用枚举器(Enumeration)和迭代器(Iterator)来遍历Vector 备注:使用for循环+get() 12. 使用System.arraycopy()来复制数组 备注:使用System.arraycopy()代替for循环,可以产生更快的代码。如: public voidcopyArray( int[]src, int[]dest){ intsize=src.length; System.arraycopy(src,0,dest,0,size); } System.arraycopy()是以native method实现的,可以直接、高效的移动原始数组到目标数组,因此它执行速度更快。 13. 优先使用数组,然后才考虑Vector和ArrayList,理由: a). Vector的get()是同步的 b). ArrayList基本上就是一个非线程同步的Vector,比Vector要快 c). ArrayList和Vector添加元素或移除元素都需要重新整理数组。 备注:不要仅仅因为手上有个数不定的数据需要存储,就毫无选择的使用Vector或ArrayList。可以考虑创建一个足够大的数组,通常这样可能会浪费内存,但性能上的收益可能超过内存方面的代价。 14. 手工优化代码 a). 剔除空白函数和无用代码 b). 削减强度 备注:以更高效的操作替换成本昂贵的操作。一个常见的优化手法是使用复式复制操作符(如+=、-=)。 c). 合并常量 备注:将变量声明为final,使得操作在编译器就进行。 d). 删减相同的子表达式 备注:可用一个临时变量代替重复的表达式。 e). 展开循环 备注:如循环次数少且已知循环次数,可展开去掉循环结构,直接访问数组元素。缺点是会产生更多代码。 f). 简化代数 备注:使用数学技巧来简化表达式。(例如从1+..+100的问题) g). 搬移循环内的不变式 备注:循环内不变化的表达式可用移至循环外,不必重复计算表达式。 15. 编译为本机代码 备注:将程序的某部分编译为本机二进制代码,然后通过JNI访问。 二、多线程 1. 对于实例(instance)函数,同步机制锁定的是对象,而不是函数和代码块。 备注:函数或代码块被声明为synchronized并非意味它在同一时刻只能有一个线程执行(同一对象不同线程调用会阻塞)。Java语言不允许将构造函数声明为synchronized。 2. 同步实例函数和同步静态函数争取的是不同的locks。 备注:两者均非多线程安全,可以使用实例变量进行同步控制,如(byte[] lock = new byte[0]),比其他任何对象都经济。 3. 对于synchronized函数中可被修改的数据,应使之成为private,并根据需要提供访问函数。如果访问函数返回的是可变对象,那么可以先克隆该对象。 4. 避免无谓的同步控制 备注:过度的同步控制可能导致代码死锁或执行缓慢。再次提醒,当一个函数声明为synchronized,所获得的lock是隶属于调用此函数的那个对象。 5. 访问共享变量时请使用synchronized或volatile 备注:如果并发性很重要,而且不需要更新很多变量,则可以考虑使用volatile。一旦变量被声明为volatile,在每次访问它们时,它们就与主内存进行一致化。如果使用synchronized,只在取得lock和释放lock时候才一致化。 6. 在单一操作(single operation)中锁定所有用到的对象 备注:如果某个同步函数调用了某个非同步实例函数来修改对象,它是线程安全的。使用同步控制时,一定要对关键字synchronized所作所为牢记在心。它锁定的是对象而非函数或代码。 7. 以固定而全局性的顺序取得多个locks(机制)以避免死锁。P/181~P/185 备注:嵌入[锁定顺序]需要额外的一些工作、内存和执行时间。 8. 优先使用notifyAll()而非notify() 备注:notify()和notifyAll()用以唤醒处以等待状态的线程,waite()则让线程进入等待状态。notify()仅仅唤醒一个线程。 9. 针对wait()和notifyAll()使用旋转锁(spin locks) 备注:旋转锁模式(spin-lock pattern)简洁、廉价,而且能确保等待着某个条件变量的代码能循规蹈矩。 10. 使用wait()和notifyAll()替代轮询(polling loops) 备注:调用wait()时会释放同步对象锁,暂停(虚悬,suspend)此线程。被暂停的线程不会占用CPU时间,直到被唤醒。如: public voidrun() { intdata; while( true){ synchronized(pipe){ while((data=pipe.getDate())==0){ try{ pipe.waite(); } catch(InterruptedExceptione){} } } // ProcessData } } 11. 不要对已锁定对象的对象引用重新赋值。 12. 不要调用stop()和suspend() 备注:stop()中止一个线程时,会释放线程持有的所有locks,有搅乱内部数据的风险;suspend()暂时悬挂起一个线程,但不会释放持有的locks,可能带来死锁的风险。两种都会引发不可预测的行为和不正确的行为。 当线程的run()结束时,线程就中止了运行。可以用轮询+变量来控制,如下代码: private volatile booleanstop; public voidstopThread() { stop= true; } public voidrun() { while(!stop){ // ProcessData } } 注意:这里使用了关键字volatile,由于Java允许线程在其 私有专用内存 中保留主内存变量的副本(可以优化),线程1对线程2调用了stopThread(),但线程2可能不会及时察觉到stop主内存变量已变化,导致不能及时中止线程。 三、类与接口 1. 实现一个final类(immutable class 不可变类)时,请遵循下列规则: a). 声明所有数据为private b). 只提供取值函数(getter),不提供赋值函数(setter) c). 在构造函数中设置有实例数据 d). 如果函数返回、接受引用final对象,请克隆这个对象。 e). 区别浅层拷贝和深层拷贝应用场景。如拷贝Vector需要使用深层拷贝。 2. 实现clone()时记得调用super.clone() 备注:不管是浅层拷贝还是深层拷贝都需要调用super.clone()。 3. 别只依赖finalize()清理内存以外的资源 备注:finalize()函数只有在垃圾回收器释放对象占用的空间之前才会被调用,回收时可能并非所有符合回收条件的对象都被回收,也无法保证是否被调用、何时调用。实现finalize()方法时记得调用super.finalize()。 4. 在构造函数内应避免调用非final函数,以免被覆写而改变初衷。 结束 书是从朋友那边借过来的,拿到手也有一段时间,磨磨唧唧好多天才看了几十页,而余下部分从上篇文章到这篇文章也不过才3-5天。发现以这种方式来看书也不错,一方面能加快速度,一方面由于要写文章更加认真细读,还能提炼把书读薄记录分享出来,实在是很适合我这样的 :) 本文转自over140 51CTO博客,原文链接:http://blog.51cto.com/over140/844924,如需转载请自行联系原作者

优秀的个人博客,低调大师

iOS开发--使用lipo命令制作模拟器与真机通用静态库

通常在项目中使用静态库的时候都会有两个版本,一个用于模拟器,一个用于真机,因为Mac和iPhone的CPU不同,才造成了这种情况。 为了模拟器与真机之间切换调试的方便,制作通用版本非常有必要。 现在有两个版本的静态库libSQLite_i386.a(模拟器)与libSQLite_arm.a(真机)。 1、打开终端,进入到这两个文件所在的目录; 2、执行:lipo -create libSQLite_i386.a libSQLite_arm.a -output libSQLite.a,这时文件就会多出libSQLite.a,这个文件即为通用静态库。 我们可以lipo -infolibSQLite.a命令,查看是否是通用的。 可以发现libSQLite.a的大小为libSQLite_i386.a与libSQLite_arm.a之和,建议发布的时候将静态库替换成真机版。 欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 572064792 | Nodejs:329118122 做人要厚道,转载请注明出处! 本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sunshine-anycall/p/3439974.html ,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android UI开发第二十二篇——android 瀑布流图片实现

自pinterest使用了瀑布流展示图片后,有很多应用开始使用瀑布流的方式,像蘑菇街,美丽说。这里的瀑布流实现使用了开源代码。 layout: <?xml version="1.0" encoding="utf-8"?> <com.dodowaterfall.LazyScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/waterfall_scroll" android:scrollbars="vertical" > <LinearLayout android:id="@+id/waterfall_container" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@android:color/white" > </LinearLayout> </com.dodowaterfall.LazyScrollView> 整个瀑布流用的是ScrollView的子类LazyScrollView。这个LazyScrollView中设置了一个监听器接口,用来监听ScrollView执行的不同阶段。接口如下: public interface OnScrollListener { void onBottom(); void onTop(); void onScroll(); void onAutoScroll(int l, int t, int oldl, int oldt); } 对于每一幅图,都用一个ImageView的子类FlowView来表示。 为了不阻塞UI线程,图片加载和图片更新都分别用不同的线程来做。这两个线程都在FlowView中。FlowView提供了加载和更新的接口给Activity调用。 瀑布流实例的主Activity是MainActivity,常量都保存在Constants类中,方便维护。 瀑布流最重要的是图片的内存回收机制,防止发生内存溢出的情况(OOM)。 参考: https://github.com/dodola/android_waterfall http://developer.android.com/training/displaying-bitmaps/process-bitmap.html 本文转自xyz_lmn51CTO博客,原文链接:http://blog.51cto.com/xyzlmn/1230768,如需转载请自行联系原作者

优秀的个人博客,低调大师

【Android游戏开发之十】(优化处理)详细剖析Android Traceview 效率检视工具

什么是TraceView?先看下百度出来的解释吧: Traceview是android平台配备一个很好的性能分析的工具。它可以通过图形化的方式让我们了解我们要跟踪的程序的性能,并且能具体到method。 关于Traceview的使用 首先,必须在程序当中加入代码,以便生成trace文件,有了这个trace文件才可以将其转化为图形。 要添加的代码如下: Java代码 // start tracing to "/sdcard/yourActivityTrace.trace" Debug.startMethodTracing("yourActivityTrace"); // ... // stop tracing Debug.stopMethodTracing(); // start tracing to "/sdcard/yourActivityTrace.trace" Debug.startMethodTracing("yourActivityTrace"); // ... // stop tracing Debug.stopMethodTracing(); Google Dev Guide当中说可以在activity的onCreate()中添加Debug.startMethodTracing(), 而在onDestroy()中添加Debug.stopMethodTracing(),但是在实际的测试时发现这种方式其实并不好用,因为通常情况下我们的activity的onDestroy()是由系统决定何时调用的,因此可能等了很长时间都不会得到这个trace文件。因此决定在onStop()中来调用Debug.stopMethodTracing()。这样当我们切换到其它activity或者点击home键的时候onStop()就会被调用,我们也就可以得到完整的trace file。 在运行程序之前,首先要保证我们的AVD是一个带有SD card的AVD,这样才能使trace文件保存到/sdcard/...当中。运行后可以任意做一些操作,然后点击home键。这是通过DDMS file explore就可以看到/sdcard/目录下有一个trace文件,现在把这个文件copy到电脑上指定的目录,假设是C:/tracefile 目录下。 可以通过命令行来执行traceview,进入tools目录后,执行 traceview C:/tracefile/yourActivityTrace.trace 之后就可以看到图形了,接下来就是按照Google Dev Guide中的解释去分析图形就OK了。 下面来看如何实现以及需要注意的地方: 实现的步骤分为三步:1.必须先在我们的模拟器中创建sdCard ;2.将我们的调试代码嵌入工程;3.利用TraceView来观察和分析代码情况; 1.对于创建模拟器的sdCard这里写出两种方式: 第一种:我们在eclipse中创建avd的时候的时候 在选择api下面有个 Sd Card 的选项,第一项填入创建sdcard的大小即可。 第二种:cmd 命令! 打开cmd 并且cd 到android sdk tool 路径下;(或者在环境变量Path中将sdk tool路径配置上,然后重新打开cmd) 使用mksdcard -l mycard 1024M F:/mysdcard.img创建了一个1G的sdcard; 使用emulator -avd my_android -sdcard F:/mysdcard.img激活sdcard! 最后在eclipse Preferences-->Android-->Launch加入-sdcardF:/mysdcard.img (此步骤就是在第一种创建方式中添加sdcard的支持) 备注1: 如果sdcard分配的空间太小,则程序追踪文件就一直记录到sd储蓄卡容量慢为止,所以调试前,要为程序生成一个适当的SD存储卡也较为重要,因为程序运行时间越长,这个追踪文件也就越大。 备注2; (如果第二种创建方式中的第二部激活出现 emulator: ERROR: the user data image is used by another emulator. aborting,请关闭模拟器,或者进入目录:/Documents and Settings / 用户 / .android /的AVD / *设备* / (比如我的目录是:C:/Documents and Settings/Administrator/.android/avd/android2.0.avd)然后删去以.lock结尾的文件夹就行(我简单解释下为什么要删除这些文件呢,其实.lock是加锁,如果程序崩溃等原因导致无法清除这些以.lock结尾的文件夹,就会出现这个问题,也就是这个avd的锁没有被释放,导致avd manager以为这个avd正在使用当中。)) 2.将我们的调试代码嵌入工程 正如我们百度到的说明一样,在程序运行的开端加上 Debug.startMethodTracing("yourActivityTrace"); 然后在onPause()中调用Debug.stopMethodTracing(); 为什么要将结束写在onPause()中而不写在onStop(),那么如果你去看api的话,你会看到,Api中介绍onPause()会在你返回和点击home按键后触发,而onStop()一般是由系统来触发,当该程序处于后台的时候,而且当内存紧张的时候,可能会调用,但是可能永远不会调用到! 备注:要记住当把调试代码加入项目中以后不要立即运行项目,而是必须在AndroidMainfest.xml中定义一条"写入SD卡的权限"那么添加权限的代码如下: <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission> 因为咱们的调试代码会在SD卡中生成一个追踪文件,也就是往SD卡中写入了数据,所以需要声明一条权限。这里必须注意哦! <?xmlversion="1.0"encoding="utf-8"?> <manifestxmlns:android="http://schemas.android.com/apk/res/android" package="com.himi" android:versionCode="1" android:versionName="1.0"> <applicationandroid:icon="@drawable/icon"android:label="@string/app_name"> <activityandroid:name=".MainActivity" android:label="@string/app_name"> <intent-filter> <actionandroid:name="android.intent.action.MAIN"/> <categoryandroid:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> <uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission> <uses-sdkandroid:minSdkVersion="4"/> </manifest> 3.运行项目并且退出项目从而得到的追踪文件,利用TraceView来进行分析代码运行状况: 打当正常运行了项目并且点击返回或者home按键就会在 sdcard中生成一个.trace的文件。sdcard 目录 在eclipse下,点击: windows-show view-other-android-File explorer 右上角的两个箭头,第一个表示从模拟器sdcard导出文件,第二个表示从PC上导入文件到sdcard中、“—”代表删除 ..... 然后我们通过cmd来运行生成的追踪文件traceview C:/name追踪文件所在的路径放在C盘,放在C盘以外别的盘的话我这里是无法正常打开traceview的不知道什么原因。 name 表示生成的.trace文件,cmd的时候不需要输入“.trace”后缀 ;然后会出现TraceView的分析窗口; 【cmd 命令! 打开cmd 并且cd 到android sdk tools 路径下;(或者在环境变量Path中将sdk tool路径配置上,然后重新打开cmd)】 注意1:如果出现一下图片这种内存溢出的问题; 解决方法:到SDK 下的tools 下 找到 traceview.bat 文件,鼠标右键-编辑(或者记事本打开),最后一行替换成这样: call java -Xms128m -Xmx512m -Djava.ext.dirs=%javaextdirs% -jar %jarpath% %* 注意2:如果出现路径不对的问题: 例如:我的 himi.trace 放在了C盘,那么我的cmd命令是: traceview c:/himi 然后回车! 但是这里要小心,因为 /h 这样可能被认为是转义字符!!!为了避免可以尽可能不要使用h,n,r,t,等等成为名字的头字母,当然还有一种就可以完全避免这种问题,例如还是我的C盘 himi.trace 文件,可以写cmd命令的时候写成: traceview c://himi 嘿嘿~要注意细节。 下面是运行起来的TranceView: 最右上角表示运行程序总共用了多少时间,从traceview画面中我们看到有各种颜色,每种颜色代表不同的函数和步骤,那么同一颜色的区域越大,就代表这个步骤运行时间越长,或者看到下面的统计表,明显可以看出除了序列 0 1 是系统函数外,2. 3.函数 占用的时间比较长,那么序列4是个自定义的函数名为 “hot”这个占用了几乎与主线程 主draw的时间一样了,那么肯定有问题。当然其实这个方法是我故意写的,就是为了来演示traceview。这个hot函数的代码如下: /** * @author Himi * @param canvas */ public void hot(Canvas canvas) { for (inti=1; i<100; i++) { Bitmapbmp=BitmapFactory.decodeResource(getResources(), R.drawable.icon); canvas.drawBitmap(bmp, i += 2, i += 2, paint); } } 很明显我在故意消耗内存和时间。 那么,在traceview的右半部统计字段中: Exclusive: 同级函数本身运行的时间 Inclusive 就是说除统计函数本身运行的时间外再加上调用子函数所运行的时间 Name:列出的是所有的调用项,前面的数字是编号,展开可以看到有的有Parent 和Children子项,就是指被调用和调用。 Incl: inclusive时间占总时间的白分比 Excl: 执行占总时间的白分比。 Calls+Recur Calls/Total: 调用和重复调用的次数 Time/Call: 总的时间。(ms) 所以traceview是个非常好的程序监视工具,可以帮助找出程序运行缓慢时的函数,让我们的代码不断完善和改进! 本文转自 xiaominghimi 51CTO博客,原文链接:http://blog.51cto.com/xiaominghimi/606325,如需转载请自行联系原作者

优秀的个人博客,低调大师

【Android UI设计与开发】1.引导界面(一)ViewPager介绍和简单实现

1.ViewPager 实现效果图 2.ViewPager 实现功能 ViewPager类提供了多界面切换的新效果,新效果有如下特征: <1>当前显示一组界面中的其中一个界面; <2>当用户通过左右滑动界面时,当前的屏幕显示当前界面和下一个界面的一部分; <3>滑动结束后,界面自动跳转到当前选择的界面中。 3.ViewPager详细说明 android-support-v4.jar是谷歌官方给我们提供的一个兼容低版本安卓设备的软件包,里面包囊了只有在安卓3.0以上可以使用的api。而viewpager就是其中之一利用它,我们可以做很多事情,从最简单的导航,到页面菜单等等。那如何使用它呢,与ListView类似,我们也需要一个适配器,他就是PagerAdapter。 ViewPager对应官方文档地址为:http://developer.android.com/reference/android/support/v4/view/ViewPager.html 4、ViewPager的使用 分三个步骤来使用它: 1、在布局文件里加入该组件 <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" > 注意:这个组件是用来显示左右滑动的界面的,如果不加载xml布局文件,他是不会显示内容的 2、加载要显示的页卡 LayoutInflater lf = getLayoutInflater().from(this); view1 = lf.inflate(R.layout.layout1, null); view2 = lf.inflate(R.layout.layout2, null); view3 = lf.inflate(R.layout.layout3, null); viewList = new ArrayList<View>();// 将要分页显示的View装入数组中 viewList.add(view1); viewList.add(view2); viewList.add(view3); 3、在Activity里实例化ViewPager组件,并设置它的Adapter(就是PagerAdapter,方法与ListView一样的),在这里一般需要重写PagerAdapter。 public class MyViewPagerAdapter extends PagerAdapter { private List<View> mListViews; public MyViewPagerAdapter(List<View> mListViews) { this.mListViews = mListViews;//构造方法,参数是我们的页卡,这样比较方便。 } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView(mListViews.get(position));//删除页卡 } @Override public Object instantiateItem(ViewGroup container, int position) {//这个方法用来实例化页卡 container.addView(mListViews.get(position), 0);//添加页卡 return mListViews.get(position); } @Override public int getCount() { return mListViews.size();//返回页卡的数量 } @Override public boolean isViewFromObject(View arg0, Object arg1) { return arg0==arg1; } } ViewPager的适配器是PagerAdapter,它是基类提供适配器来填充页面ViewPager内部,你很可能想要使用一个更具体的实现,如FragmentPagerAdapter或FragmentStatePagerAdapter。在这里需要说明一下,其实ViewPager应该和Fragment一起使用,至少谷歌官方是这么想的,但是在3.0之下,我们没有必要这么做。下面要注意,当你实现一个PagerAdapter,你必须至少覆盖以下方法: instantiateItem(ViewGroup, int) destroyItem(ViewGroup, int, Object) getCount() isViewFromObject(View, Object) PagerAdapter对应的官方文档地址为:http://developer.android.com/reference/android/support/v4/view/PagerAdapter.html 本文转自叶超Luka博客园博客,原文链接:http://www.cnblogs.com/yc-755909659/p/4282798.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

【Android游戏开发二十五】在Android上的使用《贝赛尔曲线》!

首先对于《赛贝尔曲线》不是很了解的童鞋,请自觉白度百科、google等等... 为了方便偷懒的童鞋,这里给个《贝赛尔曲线》百科地址,以及一段话简述《贝赛尔曲线》: 《贝赛尔曲线》白度百科快速地址:http://baike.baidu.com/view/4019466.htm 贝塞尔曲线又称贝兹曲线或贝济埃曲线,一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋; 上面这一段话其实就“线段像可伸缩的皮筋”这一句比较重要,也很容易理解; 至于贝赛尔曲线的实现,在Android中极其的简单,因为它是Android封装的一个方法,这个能不简单么。。。。。。只不过它隐藏的比较深,它隐藏于Path类中,方法如下: android.graphics.Path.quadTo(float x1, float y1, float x2, float y2) Since:API Level 1 此方参数解释: 第一个参数:操作点的x坐标 第二个参数:操作点的y坐标 第三个参数:结束点的x坐标 第四个参数:结束点的y坐标 从API中看出,赛贝尔曲线从API-1就开始支持了; 熟悉方法后,下面就来实现: SurfaceView框架不多讲,看过我博客的都应该知道的; 直接看MySurfaceView类,此类继承SurfaceView ,是游戏的主视图 这里为了更清晰的讲解:这里部分代码先不贴出来了,最后会整体贴出,当然源码也是免费在最后提供~ 首先是定义相关的成员变量: //贝赛尔曲线成员变量(起始点,控制(操作点),终止点,3点坐标) privateintstartX,startY,controlX,controlY,endX,endY; //Path privatePathpath; //为了不影响主画笔,这里绘制贝赛尔曲线单独用一个新画笔 privatePaintpaintQ; //随机库(让贝赛尔曲线更明显) privateRandomrandom; 本类构造函数: /** *SurfaceView初始化函数 */ publicMySurfaceView(Contextcontext){ super(context); ... //贝赛尔曲线相关初始化 path=newPath(); paintQ=newPaint(); paintQ.setAntiAlias(true); paintQ.setStyle(Style.STROKE); paintQ.setStrokeWidth(5); paintQ.setColor(Color.WHITE); random=newRandom(); ... } 接着我把绘制贝赛尔曲线封装一个方法了,函数如下: /** *绘制贝赛尔曲线 * *@paramcanvas主画布 */ publicvoiddrawQpath(Canvascanvas){ path.reset();//重置path //贝赛尔曲线的起始点 path.moveTo(startX,startY); //设置贝赛尔曲线的操作点以及终止点 path.quadTo(controlX,controlY,endX,endY); //绘制贝赛尔曲线(Path) canvas.drawPath(path,paintQ); } 最后是用户触屏监听函数以及逻辑函数: /** *触屏事件监听 */ @Override publicbooleanonTouchEvent(MotionEventevent){ endX=(int)event.getX(); endY=(int)event.getY(); returntrue; } /** *游戏逻辑 */ privatevoidlogic(){ if(endX!=0&&endY!=0){ //设置操作点为线段x/y的一半 controlX=random.nextInt((endX-startX)/2); controlY=random.nextInt((endY-startY)/2); } } 整个代码很easy~主要是贝赛尔函数的参数,尤其是操作点,操作点的各种不同可以实现不同的效果,这里我简单的统一的讲操作点设置成用户触屏点的x,y的一半,呵呵偷懒了~嘻嘻~ 我把贝赛尔的操作点写在了逻辑logic()函数中,不断的执行,并且每次利用nextInt函数得到随机的操作点,主要为了让其曲线不断的变化从而形成一个震动的曲线运动轨迹; ok,效果接图如下: 这里可能由于图片是静止的效果看起来不是很明显,大家可以运行源码来观察 ,好了~本节就这样吧;下面贴出整个MySurfaceView的源码:(最后有本项目的源码下载地址) package com.qpath; import java.util.Random; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Path; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; /** * 赛贝尔曲线 * @author Himi * */ public class MySurfaceView extends SurfaceView implements Callback, Runnable { private SurfaceHolder sfh; private Paint paint; private Thread th; private boolean flag; private Canvas canvas; public static int screenW, screenH; // -----------以上是SurfaceView游戏框架 // 贝赛尔曲线成员变量(起始点,控制(操作点),终止点,3点坐标) private int startX, startY, controlX, controlY, endX, endY; // Path private Path path; // 为了不影响主画笔,这里绘制贝赛尔曲线单独用一个新画笔 private Paint paintQ; // 随机库(让贝赛尔曲线更明显) private Random random; /** * SurfaceView初始化函数 */ public MySurfaceView(Context context) { super(context); sfh = this.getHolder(); sfh.addCallback(this); paint = new Paint(); paint.setColor(Color.WHITE); paint.setAntiAlias(true); setFocusable(true); // -----------以上是SurfaceView游戏框架 //贝赛尔曲线相关初始化 path = new Path(); paintQ = new Paint(); paintQ.setAntiAlias(true); paintQ.setStyle(Style.STROKE); paintQ.setStrokeWidth(5); paintQ.setColor(Color.WHITE); random = new Random(); } /** * SurfaceView视图创建,响应此函数 */ public void surfaceCreated(SurfaceHolder holder) { screenW = this.getWidth(); screenH = this.getHeight(); flag = true; // 实例线程 th = new Thread(this); // 启动线程 th.start(); // -----------以上是SurfaceView游戏框架 } /** * 游戏绘图 */ public void myDraw() { try { canvas = sfh.lockCanvas(); if (canvas != null) { canvas.drawColor(Color.BLACK); // -----------以上是SurfaceView游戏框架 drawQpath(canvas); } } catch (Exception e) { // TODO: handle exception } finally { if (canvas != null) sfh.unlockCanvasAndPost(canvas); } } /** * 绘制贝赛尔曲线 * * @param canvas 主画布 */ public void drawQpath(Canvas canvas) { path.reset();// 重置path // 贝赛尔曲线的起始点 path.moveTo(startX, startY); // 设置贝赛尔曲线的操作点以及终止点 path.quadTo(controlX, controlY, endX, endY); // 绘制贝赛尔曲线(Path) canvas.drawPath(path, paintQ); } /** * 触屏事件监听 */ @Override public boolean onTouchEvent(MotionEvent event) { endX = (int) event.getX(); endY = (int) event.getY(); return true; } /** * 游戏逻辑 */ private void logic() { if (endX != 0 && endY != 0) { // 设置操作点为线段x/y的一半 controlX = random.nextInt((endX - startX) / 2); controlY = random.nextInt((endY - startY) / 2); } } /** * 按键事件监听 */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return super.onKeyDown(keyCode, event); } public void run() { while (flag) { long start = System.currentTimeMillis(); myDraw(); logic(); long end = System.currentTimeMillis(); try { if (end - start < 50) { Thread.sleep(50 - (end - start)); } } catch (InterruptedException e) { e.printStackTrace(); } } } /** * SurfaceView视图状态发生改变,响应此函数 */ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } /** * SurfaceView视图消亡时,响应此函数 */ public void surfaceDestroyed(SurfaceHolder holder) { flag = false; } } 本章源码下载地址:http://www.himigame.com/android-game/386.html 本文转自 xiaominghimi 51CTO博客,原文链接:http://blog.51cto.com/xiaominghimi/606992,如需转载请自行联系原作者

优秀的个人博客,低调大师

【Android开发经验】LayoutInflater—— 你可能对它并不了解甚至错误使用

今天,看到了一篇文章讲LayoutInflater的使用方法。瞬间感觉自己对这个类确实不够了解,于是简单的看了下LayoutInflater类的源码。对这个类有了新的认识。 首先。LayoutInflater这个类是用来干嘛的呢? 我们最经常使用的便是LayoutInflater的inflate方法。这种方法重载了四种调用方式。分别为: 1.public View inflate(int resource, ViewGroup root) 2.public View inflate(int resource, ViewGroup root, boolean attachToRoot) 3.public View inflate(XmlPullParser parser, ViewGroup root) 4.public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) 这四种使用方式中。我们最经常使用的是第一种方式。inflate方法的主要作用就是将xml转换成一个View对象。用于动态的创建布局。尽管重载了四个方法。可是这四种方法终于调用的,还是第四种方式。 第四种方式也非常好理解,内部实现原理就是利用Pull解析器。对Xml文件进行解析,然后返回View对象。 我们以我们经常使用的第一种形式为例,你在重写BaseAdapter的getView方法的时候是否这样做过 public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = inflate(R.layout.item_row, null); } return convertView; } inflate方法有三个參数。各自是 1.resource布局的资源id 2.root填充的根视图 3.attachToRoot是否将载入的视图绑定到根视图中 在这个样例中,我们将root參数设为空,功能确实实现了。可是这里还隐藏着一个隐患。这样的方式并非inflate正确的使用姿势,以下我们通过一个Demo,来说一下这样使用造成的弊端。 首先,我们建立一个这样的项目 这里三个界面,一个主界面,两个測试界面,布局文件里。主界面仅仅负责界面跳转,两个測试界面都是一个简单的Listview,item布局显示效果例如以下 相应的布局文件例如以下 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="60dp" android:background="@android:color/holo_orange_light" android:gravity="center" android:orientation="vertical" > <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="11" android:textColor="@android:color/black" android:textSize="22sp" /> </LinearLayout> OneActivity的代码例如以下 public class OneActivity extends Activity { private ListView list1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_one); list1 = (ListView) findViewById(R.id.list1); list1.setAdapter(new MyAdapter(this)); } private class MyAdapter extends BaseAdapter { private LayoutInflater inflater; MyAdapter(Context context) { inflater = LayoutInflater.from(context); } @Override public int getCount() { return 20; } @Override public Object getItem(int position) { return position; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = inflater.inflate(R.layout.item_list, null); } TextView tv = (TextView) convertView.findViewById(R.id.tv); tv.setText(position+""); return convertView; } } } TwoActivity的代码例如以下 public class TwoActivity extends Activity { private ListView list2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_two); list2 = (ListView) findViewById(R.id.list2); list2.setAdapter(new MyAdapter(this)); } private class MyAdapter extends BaseAdapter { private LayoutInflater inflater; MyAdapter(Context context) { inflater = LayoutInflater.from(context); } @Override public int getCount() { return 20; } @Override public Object getItem(int position) { return position; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = inflater.inflate(R.layout.item_list, parent,false); } TextView tv = (TextView) convertView.findViewById(R.id.tv); tv.setText(position + ""); return convertView; } } } 两个文件最关键的差别就一句话。 在getView方法中,OneActivity是 convertView = inflater.inflate(R.layout.item_list, null); 在getView方法中,TwoActivity是 convertView = inflater.inflate(R.layout.item_list, parent,false); 我们先看一下显示效果。再说两者的差别 OneActivity效果 TwoActivity的显示效果 我们能够非常明显的看出来,使用第一种方式,根布局的高度设置60dp没有起作用,系统还是依照包裹内容的方式载入的,为什么会产生这样的效果呢?我们从须要inflate方法的源码中找一下答案。 首先,方式一的源码实现 public View inflate(XmlPullParser parser, ViewGroup root) { return inflate(parser, root, root != null); } 当我们使用方式一,而且第二个參数传入null的时候,默认调用的是以下的方法,而且attachToRoot是false public View inflate(int resource, ViewGroup root, boolean attachToRoot) { if (DEBUG) System.out.println("INFLATING from resource: " + resource); XmlResourceParser parser = getContext().getResources().getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } } 在这一个方法中,pull解析器将资源id转化成XmlResourceParser对象,又传给了第四种方式,所以我们须要重点看的还是第四种方式是怎样实现的 public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate"); final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context)mConstructorArgs[0]; mConstructorArgs[0] = mContext; View result = root; try { // Look for the root node. int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); if (DEBUG) { System.out.println("**************************"); System.out.println("Creating root view: " + name); System.out.println("**************************"); } if (TAG_MERGE.equals(name)) { if (root == null || !attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } rInflate(parser, root, attrs, false); } else { // Temp is the root view that was found in the xml View temp; if (TAG_1995.equals(name)) { temp = new BlinkLayout(mContext, attrs); } else { temp = createViewFromTag(root, name, attrs); } ViewGroup.LayoutParams params = null; if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } } if (DEBUG) { System.out.println("-----> start inflating children"); } // Inflate all children under temp rInflate(parser, temp, attrs, true); if (DEBUG) { System.out.println("-----> done inflating children"); } // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { root.addView(temp, params); } // Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { InflateException ex = new InflateException(e.getMessage()); ex.initCause(e); throw ex; } catch (IOException e) { InflateException ex = new InflateException( parser.getPositionDescription() + ": " + e.getMessage()); ex.initCause(e); throw ex; } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; } Trace.traceEnd(Trace.TRACE_TAG_VIEW); return result; } } 代码比較长,我们重点关注以下的代码 if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } } 这些代码的意思就是,当我们传进来的root參数不是空的时候,而且attachToRoot是false的时候,也就是上面的TwoActivity的实现方式的时候,会给temp设置一个LayoutParams參数。那么这个temp又是干嘛的呢? <pre name="code" class="java">// We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { root.addView(temp, params); } // Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || !attachToRoot) { result = temp; } 如今应该明确了吧。当我们传进来的root不是null,而且第三个參数是false的时候。这个temp就被增加到了root中。而且把root当作终于的返回值返回了。而当我们设置root为空的时候,没有设置LayoutParams參数的temp对象。作为返回值返回了。 因此,我们能够得出以下的结论: 1.若我们採用convertView = inflater.inflate(R.layout.item_list, null);方式填充视图。item布局中的根视图的layout_XX属性会被忽略掉。然后设置成默认的包裹内容方式 2.假设我们想保证item的视图中的參数不被改变,我们须要使用convertView = inflater.inflate(R.layout.item_list, parent,false);这样的方式进行视图的填充 3.除了使用这样的方式,我们还能够设置item布局的根视图为包裹内容,然后设置内部控件的高度等属性。这样就不会改动显示方式了。 最后,给出那篇文章的链接http://blog.jobbole.com/72156/大家能够去看看 本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5230670.html,如需转载请自行联系原作者

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册