首页 文章 精选 留言 我的

精选列表

搜索[表格控件],共10000篇文章
优秀的个人博客,低调大师

iOS开发-UITableView表格优化

之前的一篇文章大概讲述了一下UITableView的使用,UITableView在iOS的地位和ListView在Android中的地位基本上算是不相上下,关于ListView的优化网上的也有很多文章。UITableView苹果公司本身就已经优化了其中的功能,不管你有多少数据,每次加载的时候只是加载当前页面的数据,以免造成不必要的内存占用。一个非常常见的优化就是使用Identifier,也就是唯一标示,将页面中不用的对象放在缓存池中,如果有新的对象出现从缓存池中取出。 页面布局 页面布局还是跟上篇文章一样,一个TableView: 不过需要额外的工作的时本次暂时的是服装信息,通过弹框修改服装的价格,需要新建一个服装类,实现UIAlertViewDelegate协议,头文件中的声明: 1 2 3 @interface ViewController : UIViewController <UITableViewDataSource,UITableViewDelegate,UIAlertViewDelegate> @property (weak, nonatomic ) IBOutlet UITableView *tableView; @end 服装类的定义: 1 2 3 4 5 6 7 8 9 @interface Dress : NSObject @property (strong, nonatomic ) NSString *dressName; @property (strong, nonatomic ) NSString *dressDetial; @property (strong, nonatomic ) NSString *dressImage; @end 优化与实现 优化之前先实现一些必要的功能,以及一些方法的使用,上篇文章只是涉及了其中的一部分,为了更好的理解,可以先看下实现的效果: 定义存储数据的数组: 1 2 3 4 5 6 7 @interface ViewController () { NSArray *imageArr; NSArray *dressArr; NSMutableArray *dressList; } @end 初始化数据: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 - ( void )viewDidLoad { [ super viewDidLoad]; // Do any additional setup after loading the view. imageArr=[[ NSArray alloc]initWithObjects:@ "dress1.jpeg" ,@ "dress2.jpg" ,@ "dress3.jpg" , nil ]; dressArr=[[ NSArray alloc]initWithObjects:@ "七匹狼" ,@ "森马" ,@ "杰克琼斯" ,@ "美特斯邦威" ,@ "以纯" ,@ "真维斯" ,@ "海南之家" , nil ]; dressList=[ NSMutableArray arrayWithCapacity:30]; for ( NSInteger i=0; i<30; i++) { Dress *dress=[[Dress alloc]init]; NSInteger imageRandom=arc4random_uniform(3); NSInteger dressRandom=arc4random_uniform(7); NSInteger price=arc4random_uniform(1000)+100; dress.dressImage=imageArr[imageRandom]; dress.dressName=dressArr[dressRandom]; dress.dressDetial=[ NSString stringWithFormat:@ "促销价:%ld" ,( long )price]; [dressList addObject:dress]; } } 设置行数: 1 2 3 - ( NSInteger )tableView:(UITableView *)tableView numberOfRowsInSection:( NSInteger )section{ return [dressList count]; } 设置分组: 1 2 3 - ( NSInteger )numberOfSectionsInTableView:(UITableView *)tableView{ return 1; } 设置单元格内容,通过reuseIdentifier设置重用的单元格,dequeueReusableCellWithIdentifier取出可以重用的单元格: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:( NSIndexPath *)indexPath{ static NSString *flag=@ "cacheCell" ; //生成唯一的标记 UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:flag]; if (cell== nil ) { //设置需要显示详情的样式 cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:flag]; } Dress *dress=dressList[indexPath.row]; UIImage *image=[UIImage imageNamed:dress.dressImage]; [cell.imageView setImage:image]; [cell.imageView setFrame:CGRectMake(0, 0, 80, 50)]; [cell.textLabel setText:dress.dressName]; //设置展示小箭头 [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator]; //设置Tag // [cell setTag:indexPath.row]; [cell.detailTextLabel setText:dress.dressDetial]; NSLog (@ "获取更新之后的行:%ld" ,indexPath.row); return cell; } 选中行之后的弹框设置: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 - ( void )tableView:(UITableView *)tableView didSelectRowAtIndexPath:( NSIndexPath *)indexPath{ UIAlertView *alterView=[[UIAlertView alloc] initWithTitle:@ "服装价格:" message: nil delegate: self cancelButtonTitle:@ "取消" otherButtonTitles:@ "修改" , nil ]; [alterView setAlertViewStyle:UIAlertViewStylePlainTextInput]; //选中的Cell UITableViewCell *cell=[tableView cellForRowAtIndexPath:indexPath]; UITextField *textField=[alterView textFieldAtIndex:0]; //设置修改的服装信息 [textField setText:cell.detailTextLabel.text]; [textField setTag:indexPath.row]; [alterView show]; } UIAlterView中的点击事件: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 - ( void )alertView:(UIAlertView *)alertView clickedButtonAtIndex:( NSInteger )buttonIndex{ if (buttonIndex==1) { UITextField *textField=[alertView textFieldAtIndex:0]; NSArray *selectedRow=[ self .tableView indexPathsForSelectedRows]; NSIndexPath *indexPath=selectedRow[0]; Dress *dress=dressList[indexPath.row]; dress.dressDetial=textField.text; NSLog (@ "%@---%ld" ,textField.text,( long )indexPath.row); [ self .tableView reloadRowsAtIndexPaths:selectedRow withRowAnimation:UITableViewRowAnimationRight]; } } 设置行高: 1 2 3 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:( NSIndexPath *)indexPath{ return 50; } 关于UITable的优化: 1.最常用的就是不重复生成单元格,很常见,很实用; 2.使用不透明的视图可以提高渲染速度,xCode中默认TableCell的背景就是不透明的; 3.如果有必要减少视图中的条目,本文中设置textLabel,detialTextLabel,imageView,accessoryType; 4.更新条目的时候不要整体更新,更新选中的即可,建议reloadRowsAtIndexPaths,而不是使用reloadData; 本文转自Fly_Elephant博客园博客,原文链接:http://www.cnblogs.com/xiaofeixiang/p/4248066.html,如需转载请自行联系原作者

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

浅谈EditText控件的inputType类型

android:inputType="none"--默认 android:inputType="text"--输入文本字符 android:inputType="textCapCharacters"--字母大写 android:inputType="textCapWords"--单词首字母大小 android:inputType="textCapSentences"--仅第一个字母大小 android:inputType="textAutoCorrect"--自动更正 android:inputType="textAutoComplete"--前两个自动完成 android:inputType="textMultiLine"--多行输入 android:inputType="textImeMultiLine"--输入法多行 android:inputType="textNoSuggestions"--不提示 android:inputType="textUri"--URI和URL格式 android:inputType="textEmailAddress"--电子邮件地址格式 android:inputType="textEmailSubject"--邮件主题格式 android:inputType="textShortMessage"--短消息格式 android:inputType="textLongMessage"--长消息格式 android:inputType="textPersonName"--人名格式 android:inputType="textPostalAddress"--邮政格式 android:inputType="textPassword"--密码格式 android:inputType="textVisiblePassword"--密码可见格式 android:inputType="textWebEditText"--作为网页表单的文本格式 android:inputType="textFilter"--文本筛选格式 android:inputType="textPhonetic"--拼音输入格式 android:inputType="number"--数字格式 android:inputType="numberSigned"--有符号数字格式 android:inputType="numberDecimal"--可以带小数点的浮点格式 android:inputType="phone"--拨号键盘 android:inputType="datetime" 日期时间 android:inputType="date"--日期键盘 android:inputType="time"--时间键盘 其中大多数是用不到的,这里总结一下常用的几种键盘效果 1、numberDecimal(可以带小数点的浮点格式) 只可以输入0-9数字 和 小数点 ,即只浮点数 2、number (数字格式) 只可以输入0-9数字,与numberDecimal区别是 不能输入小数点 ,点击0-9外的都无效 3、textPassword (密码格式) 默认为手机当前设置的输入键盘类型,附带效果,输入的字符会变成星号* ,一般用于密码框的输入 。

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

Android中自定义控件

其他直接上代码 ?[Copy to clipboard] Downloadzuiniuwang.java <spanstyle="font-size:14px">importandroid.content.Context; importandroid.view.ViewGroup; importandroid.widget.ImageView; importandroid.widget.LinearLayout; importandroid.widget.RelativeLayout; importandroid.widget.TextView; publicclassMyViewTestextendsRelativeLayout{ privatefinalintWC=LinearLayout.LayoutParams.WRAP_CONTENT; privatefinalintFP=LinearLayout.LayoutParams.FILL_PARENT; privatefinalstaticStringTAG=MyViewTest.class.getSimpleName(); /** * *@paramcontext *@parambackgroudPic背景图片的ID为空则为默认 *@paramimagePic按钮前面的图片为空则为默认 *@paramtext按钮的文本为空则为默认 */ publicMyViewTest(Contextcontext,IntegerbackgroudPic,IntegerimagePic,Stringtext){ super(context); //TODOAuto-generatedconstructorstub if(backgroudPic==null) this.setBackgroundResource(R.drawable.my_music_button); else this.setBackgroundResource(backgroudPic); ImageViewimageView=newImageView(context); if(imagePic==null) imageView.setImageResource(R.drawable.style1_button_icon_normal); else imageView.setImageResource(imagePic); imageView.setId(1); RelativeLayout.LayoutParamslp1=newRelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); lp1.addRule(RelativeLayout.RIGHT_OF,1); TextViewtextView=newTextView(context); if(text==null) textView.setText("mycustombutton"); else textView.setText(text); this.addView(imageView); this.addView(textView,lp1); } } 其中R.drawable.my_music_button 是一个XML文件,定义了按钮的选中点击等事件发生时,背景图片的变化,直接更改文件中相应的图片便可 ?[Copy to clipboard] Downloadzuiniuwang.xml <spanstyle="font-size:14px"><?xmlversion="1.0"encoding="UTF-8"?> </span> <selectorxmlns:android="http://schemas.android.com/apk/res/android"><spanstyle="font-size:14px"> </span><itemandroid:drawable="@drawable/my_button_bg"android:state_pressed="true"><spanstyle="font-size:14px"> </span><itemandroid:drawable="@drawable/my_button_bg"android:state_focused="true"><spanstyle="font-size:14px"> </span><itemandroid:drawable="@drawable/my_music_button_bg"><spanstyle="font-size:14px"> </span></item></item></item></selector><spanstyle="font-size:14px"> </span> 本文转自 最牛傻蛋 51CTO博客,原文链接:http://blog.51cto.com/zuiniuwang/718260,如需转载请自行联系原作者

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

.NET控件Designer架构设计

总体结构 Designer总体上由三大部分组成:View,ViewModel和Model,这个结构借鉴了流行的MVVM模式。这三部分的职责分工是: View 负责把ViewModel以图形的方式展现出来,它主要在处理画法。View适合用xaml来表达,对于某些复杂的layout,仍然会需要写一些code,但这些code不涉及业务逻辑。和MVVM的区别是,我们只是在简单输入的情况下,采用了Behavior模式,对于复杂的输入,由于判断用户的意图需要参考许多其它信息,可能要用到很多Service,或者查阅很多的状态信息,这些代码写在View端不合适,我们就直接把事件发给了ViewModel,由ViewModel去处理。View和平台相关,不同平台(WPF、SL,WP7)的xaml可能不同,代码也不同。 ViewModel 主要负责逻辑的处理,接收Event和Command,判断用户意图,改变数据,并反馈给View。ViewModel既有数据,又能响应事件,而且是一棵树,所以它本质上就是一个view,只不过是一个抽象的View,它把琐碎的画法丢给了真正的View,只关心那些和逻辑有关的数据。ViewModel和View有一定的对应关系,但它的结点比View要少得多,因此比直接在View上进行逻辑处理要简单得多。由于ViewModel的数据和操作都是针对抽象的概念进行的,因此它和平台无关。为了方便对ViewModel中的逻辑操作进行管理,我们引入了Service和Feature的概念,Service是向其它模块提供支持的内部模块,是系统的基础,所有的Service构成了系统的骨架。Feature是实现系统外部功能的模块,Feature之间没有依赖关系,它们只依赖于Service。系统中的Service不多,而且只关注最重要的逻辑,代码量不大,所以Service都是经过精心设计和良好的测试,具有很强的稳定性。feature是系统的皮肉,它直接暴露给用户,关注很多细节,代码量大,容易变化,由于feature和feature之间没有依赖性,所以这种变化不会对其它模块造成影响,利于渐进式的开发。 Model 是数据,按照业界大师的说法,Model是纯粹的数据。但我很怀疑这个说法,如果Model是纯粹的数据,那它就没有存在的必要,因为ViewModel上也有数据,何必要把数据存两份呢,同步起来还挺麻烦。我的理解是,Model上是有逻辑的,只是这些逻辑是属于另一个领域的范围了。比如Designer的Model,就是Runtime的control,这些Control是有逻辑的,但它们的逻辑已经和Designtime没有任何关系。ViewModel和Model的关系是,ViewModel操纵Model,但同时要监测Model的变化,和Model同步。如果我们清楚除了ViewModel外,不会有其它的模块去修改Model(这种情况对于一些简单的Designer是正常的),那么ViewModel和Model的关系可以更简单一些,只有ViewModel改变Model,上图中ViewModel和Model之间的箭头就只需要保留左边那一个(图中的箭头表示数据传递)。 从上面的介绍,我们可以看出,View和Model在DesignTime下都是比较简单的,复杂度主要在ViewModel,我们需要进一步对它阐述。 ViewModel层的结构 我们前面提到,Designer主体结构分成三大部分:View,ViewModel,Model,这里的概念是一个宏观概念,代表它所在的那一层里的所有结构。我们现在讨论ViewModel这一层,它里面除了一个ViewModel树,还有一些Service和许多的Feature。我们知道,图形软件的功能,不外乎就是处理用户的鼠标键盘输入,然后改变数据,最后以可视化的方式反馈给用户,因此,我们只要分析清楚我们的软件是如何来应对这样一个输入输出过程就可以了。 我们看上图,红色虚线内的结构都是在处理输入,红色虚线外的部分在处理输出(展现),可见对于Designer,输入非常复杂,输出比较简单。我们先分析简单的输出:ViewModel时刻监视着Model的变化,一旦发现Model发生了变化,就改变自己的Property以同步,注意这里的Model不一定实现了INotifyPropertyChanged接口,因此这种同步可能不能借用绑定。但ViewModel一定是DependencyObject,或者实现了INotifyPropertyChanged接口,所以当ViewModel的属性变化后,View通过绑定会让展现和数据保持一致,输出过程就完成了。 对于输入,我们需要针对不同的情况进行考虑,基本上,我们可以把输入分成两大类:简单输入和复杂输入。 什么是简单输入? 就是整个输入处理过程很简单,牵涉的模块很少,Command有明确的接受对象。常见的Adorner上的行为,大部分都是如此。举一个具体的例子,有一个Button,当它被选中的时候,会出现一个Adorner,上面有一个Slider,调整这个Slider,Button的透明度会随着变化。要处理这个Slider对Model的改变,最简单的做法就是把Slider双向绑定到对应的Adorner ViewModel的某个属性,即使不能用双向绑定,也可以通过Behavior模式调用对应ViewModel的Command。整个过程只涉及到一个Adorner View,一个Adorner ViewModel和一个Button Control,和系统的其它部分没有什么关系,这类输入行为用双向绑定或者Behavior模式处理最合适。 什么是复杂输入呢? 就是整个输入处理过程涉及到的模块比较多,受很多系统状态的影响,输入没有明确的接收对象,充满了变化。这类行为在Designer中也很多。举一个Multirow Template Designer的例子,一个CellView上收到一个MouseLeftButtonDown事件,View应该怎么处理呢?它会调用ViewModel的什么Command呢?CellView需要先判断用户的意图,但这个判断比较有难度。用户有可能是想选中这个Cell,如果是这样需要执行Selection Command,但是如果这个时候Designer处于Tab Order模式,那就不允许选择,可能是用户想改变Tab order的值。也有可能是用户刚才选择了一个ToolboxItem,现在是想创建一个Cell,还有可能是用户想移动Cell,要进行这些判断,必须要借助其它Service和查询系统中某些状态,如果判断出来是选择,还得检查这个时候的键盘状态,检查目前是否支持扩展选择,在扩展选择模式下,按Control键和Shift键的行为不一样。我们还得检查当前Cell是否已经被选中,如果已经被选中,就需要反选,这需要我们查询Selection Service。如果我们发现Cell是可以移动的,那么MouseLeftButtonDown的处理又得注意了,如果Cell没有被选择,要先选中Cell,如果Cell已经被选中了,不能立即反选,要看用户是否后续有移动的操作,反选必须放到MouseLeftButtonUp中进行。还要考虑到,今后可能需要增加新的Feature,比如增加一个移动画布的功能,用户先在Toolbar上单击了一个手型Icon的Command,然后再在CellView上单击了一下,这个时候以前的判断都无效,因为用户现在是要移动整个画布,那么我们很可能得去修改以前的CellView的Code。总之,View在处理某些事件的时候,需要知道的东西太多,只靠ViewModel提供的Property远远不够,ViewModel层必须把整个结构(所有的Service和各种状态)完全暴露给View层,这样显然不符合我们模块划分的思路。因此,对于这类复杂输入,我们让View什么都不处理,而是把事件转发给ViewModel层去处理。 除了某些事件处理很复杂,某些Command的处理也比较麻烦,比如菜单上的cut,copy,paste,delete等,这些Command没有明确的接收对象,最终由谁来处理需要根据系统当时的各种状态决定。为了解决这类Command,我们必须设计一个Command的路由机制,让那些关心这个Command的feature能够按照一定的优先级来处理这个Command. 为了处理上述的复杂输入,我们学习wpf designer,设计了一个比较复杂的机制。我们设计了一个叫Tool的类,它有一个Task集合,按照一定的优先级把Command交给每个Task处理。Task从哪儿来呢?Task属于Feature,当一个Feature认为它需要监听某些Command时,它会把自己的Task添加到Tool的Tasks中。事实上Task并没有直接处理Command,Task内部有一个CommandBinding集合,它负责处理Command。Task的Commandbinding在执行代码时,修改ViewModel的属性,或者执行一个ViewModel的Command。 对于View层转发给ViewModel层的Event,在处理中会被先翻译成Command,然后按照前面的Command流程处理。在这个过程中,需要经历下面两个步骤: 第一步 View接到鼠标键盘事件后,会调用InputService的一个函数PerformInput,把事件转发给InputService。InputService会对这个事件进行预处理,然后再转发出去。预处理解决两个问题:1.把针对View的事件,转换成针对ViewModel的事件。因为ViewModel就是一个抽象的View,如果把事件转换成了针对ViewModel的事件(就是把事件的参数Sender转换成对应的ViewModel,EventArgs转变成适合于ViewModel的EventArgs),我们就可以按照以前熟悉的windows事件处理思路来处理ViewModel的事件,把View彻底屏蔽掉。2.添加或改变一些事件,以方便后续的处理。Designer有一些频率特别高的操作,比如Drag,系统的默认事件比较弱,或者没有对应的事件,如果我们在这儿进行一些强化,后面的处理就会减少很多麻烦。 第二步 InputService对事件进行完预处理后,会把事件交给Tool。Tool不但可以对Command派发,还能对Event进行派发,因为Task中除了有CommandBinding,还有InputBinding,InputBinding用于处理事件。事件被处理完后,会生成一个Command,这个过程就是把事件翻译成Command的过程。翻译成的Command,会发给Tool处理,绕这个圈是为了和前面的Command处理流程保持一致。 在和大家的讨论中,觉得输入处理的流程太复杂,尤其是我开始的时候,为了减少ViewModel层的信息入口,不建议View去直接改变ViewModel,所有事件都转发给ViewModel层来处理。大家发现,如果那样做,即使做一个很简单的输入,都要绕很大一个圈子,非常麻烦。因此,对于简单的输入处理,我们认为应该用双向绑定或者Behavior模式,直接修改ViewModel,只对于那种比较复杂的输入,才把事件转发给ViewModel。这样一来,这个图变得似乎更复杂了,但我经过仔细考虑,觉得不能删减,因为Designer有些输入处理的流程确实非常复杂,过于简单的结构会导致后面写feature的时候需要考虑更多的问题。 另外说一下Tool,大家不大适应这个结构。因为按照我们以前的思路,即使事件交给ViewModel层处理,经过预处理后,InputService也应该直接把事件派发给对应的ViewModel,即使要路由,也可以学Wpf的路由机制,那样大家都比较熟悉。但我认为,那样的设计会让大量的逻辑写到ViewModel中,和ViewModel绑得比较死,这样会有两个大的缺点: 复杂输入处理 逻辑往往跨越多个ViewModel,本来是一个完整的逻辑,不得不分片写在不同的ViewModel中,依靠全局变量或者Service来协调。比如我们在Winform Designer中,就设计了一个DragService,用得非常频繁,原因就是在Drag中,不同的View需要协作来完成一些任务,它们只能通过DragService来协调。但现在这种机制下,就不需要DragService了。 对原有的行为进行修改很困难 一个典型场景就是,在某种状态下,需要禁止掉某些原有的行为。在Winform Designer下,我们只能有两种处理方式:一,修改原来的Code,增加判断条件,这种方式很容易搞出来新的Bug。二,在原来的View上盖上一个透明的View,把事件劫持掉,这种方式属于比较变态的方式,系统中如果用多了,会让后面的人很难理解原有的设计。微软的Winform Designer在处于这种情况时有一个经典的变态处理,它需要放一个Runtime的Control在Designer上,但不想让它的行为在Designer中起作用,或者在某些情况下有选择的让它起作用,它用了hook技术,劫持windows消息,如果有需要,可以选择性的放过去一些消息。Visual Studio中这类东西用得比较多,导致即使你按正常的方式放一个Control在Visual sdudio中,它有时工作也不正常,因为它的某些消息被hook劫持掉了。wpf中提供了Preview message,在某些情况下能够简化这类问题的处理,但我相信它的灵活性还是远远不如Tool这种把消息集中起来处理的方式,因为这种机制把逻辑彻底从ViewModel中剥离出来了,谈不上需要改变哪一个ViewModel的行为,因为ViewModel没有控制行为的代码,所有行为都属于外面的Feature,只要Feature发生变化,对应的ViewModel的“行为”自然就发生变化。 当然,Tool这种把所有消息集中处理的方式也有缺点,就是模块间的干扰非常严重,就相当于编程语言中的全局变量,方便了使用,但带来了干扰。因此我们推荐复杂的输入用这种方式,简单的输入用Behavior模式,直接修改ViewModel,或者通过DelegateCommand,把View的事件直接转发给ViewModel处理,绕过这个机制。但是,我们一定要意识到,绕过这个机制会带来的问题,就是后面要改变原有的行为是不行的,因为消息的传输过程中没有留下改变的控制点,只能去修改原有的View和ViewModel的代码。在designer中,这类简单输入方式主要应该用于Adorner,因为Adorner一般都是临时使用一下,输入简单,即使后面发现需要改变它的行为,不得已可以换一个AdornerModel和AdornerView,也不会对系统造成多大影响。但如果你要把Multirow Template designer中SectionViewModel和SectionView,或者CellViewModel和CellView换了,那影响就大了去了。所以我们今后在选择哪种输入处理方式时,一定要充分考虑到后面变化的需要。 View和ViewModel的对应关系 讨论中大家觉得View和ViewModel的对应关系比较复杂,所以这儿单独花一节来谈谈它们的关系。举一个大家熟悉的MultiRow的例子,现在假设有一个SectionViewModel,它的Chilren中有两个Cell,分别是CellViewModel1和CellViewModel2,现在我们看这个ViewModel Tree如何展现,事件如何传递,HitTest是如何实现的。 先看一下我们会怎样来设计View,为了便于用Xaml表达,我们一般会用UserControl来表达View,虽然CustomControl也能用Xaml,但它的xaml一般要写到Resource中,所以我们一般不用。我们现在有两个类:SectionViewModel和CellViewModel,因此,对应的我们会设计两个UserControl,分别叫做SectionView和CellView。这儿我要说明的是,由于CellView很简单,做产品的时候也许不会单独为它用一个UserControl,而是在Section的Xaml里直接表达了,甚至MultiRow的整个Template都用一个UserControl描述。在这里为了方便阐述概念,我们把两个View看成是两个独立的UserControl。 怎样来设计SectionView呢?我们会在UserControl中放一个ItemsControl,把它的ItemsSource邦定到datacontext的Chilren属性上,然后把ItemsPanel设置成Canvas,在ItemTemplate中指定用CellView来展现CellViewModel,当然,我们也可以用隐式DataTemplate来表达。 CellView呢?我们就在UserControl中放一个Border,把Border的Background绑定到DataContext的Background就可以了。 当外部某个对象把SectionView加载到VisualTree上时,它会负责把SectionView的DataContext指向SectionViewModel(这个对象很可能也是一个DataTemplate),通过绑定,所有的CellViewModel都会有对应的CellView,最后的VisualTree会如图中所示。 我们看到,VisualTree的Visual结点明显多于ViewModel的结点,那么它们的对应关系是如何的呢?有两条原则: 1.一个ViewModel有且只有一个Visual和它对应,我们可以把这个Visual叫做这个ViewModel的View。一个Visual对应一个或零个ViewModel。 2.如果ViewModel A是ViewModel B的祖先,那么对应的Visual A也应该是Visual B的祖先,如果ViewModel A不是ViewModel B的祖先,那么对应的Visual A也不应该是Visual B的祖先。 按照我们的设计,ViewModel和Visual对应关系如上图,红色结点的Visual就是ViewModel对应的View。那么这个对应关系是怎么记录的,因为今后的很多逻辑会依赖这个数据。 首先,我们会在设计的时候认定ViewModel和Visual的对应关系,如上图,我们认为SectionViewModel对应SectionView(UsrControl),CellViewModel对应CellView(UserControl),所以我们会在这两个UserControl的Xaml中设置一个附加属性ViewProperties.ViewModel,把它绑定到DataContext上,这样就让View指向了ViewModel,在附加属性ViewProperties.ViewModel的PropertyChanged事件里,我们会创建一个IViewModel对象赋给对应的ViewModel的View属性,IViewModel会抓着真正的Visual。这样ViewModel和View的双向对应关系就建立起来了。 如何解决HitTest? View层会实现一个IViewService,里面有一个函数:IEnumerable<IView> FindViews(Point p),其它对象可以调用这个函数来拿到HitTest的IView,再通过IView拿到ViewModel(我想这一步可以简化到直接返回ViewModel,目前是返回的IView)。View层是如何查找View的呢?它会调用VisualTreeHelper的HitTest,找到Hit的Visual,然后遍历父Visual,找到某个有对应ViewModel的Visual,那么这个Visual就是Hit的View了。 解决了HitTest,InputService对事件的预处理就简单了,它拿到一个Mouse事件参数后,会得到Mouse的坐标,然后调用IViewService的FindView函数,就可以知道这个Mouse事件是针对哪一个ViewModel,然后把Sender和EventArgs都转换成适合于ViewModel的,再转发出去就可以了。 与PropertyGrid交互 会有一个专门的Service来负责与PropertyGrid交互,展现在PropertyGrid上的对象是ViewModel创建的一个对象,因此受ViewModel控制,ViewModel可以决定是把自己交给PropertyGrid,或者设计另一个类型,融合Model的Property和Design Time的Property。 序列化 序列化由专门的Service来完成,Service中登记有不同类型的ViewModel的Serializer,默认的Serializer会调用Runtime的序列化方法直接把RuntimeControl序列化成文本,这个序列化设计学习Winform designer的序列化架构。 Undo/Redo 从我们上面的设计看,所有的输入都要经过ViewModel,所以在ViewModel上做Undo/Redo。系统中有一个UndoService,当一个ViewModel的Property被改变时,会通知UndoService,UndoService会把改变前的值记录下来。值得注意的是,不是所有的ViewModel的属性都需要Undo,这点具体设计时根据需要判断,一般来说,Runtime的Property都需要,非Runtime的Property可能有部分需要。我考虑过根据Undo和序列化的要求,在ViewModel和runtime control之间再隔离出来一个层次,比如叫ModelItem,这样结构上更清楚一些。但多一个层次开发的时候会多不少工作,觉得不划算,目前暂定由ViewModel兼任这个职责。 架构如何应对未来的变化 目前的架构是针对复杂Designer设计的架构,如果未来的Designer比较简单,这个架构是不是有点高射炮打蚊子呢?我的想法万一未来的Designer比较简单,这个架构可以从下面三个地方去简化: 1.砍掉输入的无关事件和无关Feature.目前的架构添加了一些事件,如Drag,实现了一些和这些事件有关的核心Feature,如果未来不需要,可以砍掉。因为按现在的架构,Feature是独立的,彼此互不影响,把这类Feature删掉即可。事件也一样,删减事件不影响整体流程。 2.如果仍然觉得复杂,可以把Tool,Task等概念删掉,增加Tool,task的概念,是为了让输入集中处理,拥有更强的灵活性。如果把这些概念删除掉,InputService直接把事件派发给对应的ViewModel就可以了,这就相当于winform的事件机制,由ViewModel直接处理事件。这一步把输入大大的简化了。 3.如果上面简化还不够,把InputService也干掉,由View利用Behavior处理输入,然后调用ViewModel的Command,这就变成了经典的MVVM模式,到这一步应该化到最简了。 本文转自 powertoolsteam 51CTO博客,原文链接:http://blog.51cto.com/powertoolsteam/473757,如需转载请自行联系原作者

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

Android:UI控件GestureOverlayView、gesture、手势

XML代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <android.gesture.GestureOverlayView xmlns:android= "http://schemas.android.com/apk/res/android" xmlns:tools= "http://schemas.android.com/tools" android:id= "@+id/gestureOverlayView1" android:layout_width= "match_parent" android:layout_height= "match_parent" android:layout_alignParentLeft= "true" android:layout_alignParentTop= "true" > <RelativeLayout android:layout_width= "match_parent" android:layout_height= "match_parent" tools:context= ".MainActivity" > <TextView android:id= "@+id/textView1" android:layout_width= "wrap_content" android:layout_height= "wrap_content" android:layout_alignParentTop= "true" android:layout_centerHorizontal= "true" android:layout_marginTop= "30dp" android:text= "Large Text" android:textAppearance= "?android:attr/textAppearanceLarge" /> <CheckBox android:id= "@+id/checkBox1" android:layout_width= "wrap_content" android:layout_height= "wrap_content" android:layout_below= "@+id/textView1" android:layout_centerHorizontal= "true" android:layout_marginTop= "24dp" android:text= "CheckBox" /> </RelativeLayout> </android.gesture.GestureOverlayView> 本文转自 glblong 51CTO博客,原文链接:http://blog.51cto.com/glblong/1228590,如需转载请自行联系原作者

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

Android Folding View(折叠视图、控件)

版本号:1.0 日期:2014.4.21 版权:© 2014 kince 转载注明出处 非常早之前看过有人求助以下这个效果是怎样实现的, 也就是側滑菜单的一个折叠效果,事实上关于这个效果的实现,谷歌的一名project师已经完毕。并开放源代码到devbytes上面了。 如以下所看到的: 地址是: https://android.googlesource.com/platform/development/+/master/samples/devbytes/graphics/ 。还有对应的视频讲解: DevBytes: Folding Layout - YouTube ,这个想看的话须要fq。执行效果例如以下: 那后来就有人在此基础之上加入了側滑菜单效果,包含DrawerLayout和PaneLayout两种,此项目的github地址是 Folding-Android,执行效果例如以下: 1、DrawerLayout 2、 PaneLayout 以下主要环绕实现原理来展开,以谷歌的devbytes为例。看一下它的自己定义ViewGroup:FoldingLayout。代码例如以下, /* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.foldinglayout; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.Shader.TileMode; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; /** * The folding layout where the number of folds, the anchor point and the * orientation of the fold can be specified. Each of these parameters can * be modified individually and updates and resets the fold to a default * (unfolded) state. The fold factor varies between 0 (completely unfolded * flat image) to 1.0 (completely folded, non-visible image). * * This layout throws an exception if there is more than one child added to the view. * For more complicated view hierarchy's inside the folding layout, the views should all * be nested inside 1 parent layout. * * This layout folds the contents of its child in real time. By applying matrix * transformations when drawing to canvas, the contents of the child may change as * the fold takes place. It is important to note that there are jagged edges about * the perimeter of the layout as a result of applying transformations to a rectangle. * This can be avoided by having the child of this layout wrap its content inside a * 1 pixel transparent border. This will cause an anti-aliasing like effect and smoothen * out the edges. * */ public class FoldingLayout extends ViewGroup { public static enum Orientation { VERTICAL, HORIZONTAL } private final String FOLDING_VIEW_EXCEPTION_MESSAGE = "Folding Layout can only 1 child at " + "most"; private final float SHADING_ALPHA = 0.8f; private final float SHADING_FACTOR = 0.5f; private final int DEPTH_CONSTANT = 1500; private final int NUM_OF_POLY_POINTS = 8; private Rect[] mFoldRectArray; private Matrix [] mMatrix; private Orientation mOrientation = Orientation.HORIZONTAL; private float mAnchorFactor = 0; private float mFoldFactor = 0; private int mNumberOfFolds = 2; private boolean mIsHorizontal = true; private int mOriginalWidth = 0; private int mOriginalHeight = 0; private float mFoldMaxWidth = 0; private float mFoldMaxHeight = 0; private float mFoldDrawWidth = 0; private float mFoldDrawHeight = 0; private boolean mIsFoldPrepared = false; private boolean mShouldDraw = true; private Paint mSolidShadow; private Paint mGradientShadow; private LinearGradient mShadowLinearGradient; private Matrix mShadowGradientMatrix; private float [] mSrc; private float [] mDst; private OnFoldListener mFoldListener; private float mPreviousFoldFactor = 0; private Bitmap mFullBitmap; private Rect mDstRect; public FoldingLayout(Context context) { super(context); } public FoldingLayout(Context context, AttributeSet attrs) { super(context, attrs); } public FoldingLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected boolean addViewInLayout(View child, int index, LayoutParams params, boolean preventRequestLayout) { throwCustomException(getChildCount()); boolean returnValue = super.addViewInLayout(child, index, params, preventRequestLayout); return returnValue; } @Override public void addView(View child, int index, LayoutParams params) { throwCustomException(getChildCount()); super.addView(child, index, params); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { View child = getChildAt(0); measureChild(child,widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { View child = getChildAt(0); child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight()); updateFold(); } /** * The custom exception to be thrown so as to limit the number of views in this * layout to at most one. */ private class NumberOfFoldingLayoutChildrenException extends RuntimeException { public NumberOfFoldingLayoutChildrenException(String message) { super(message); } } /** Throws an exception if the number of views added to this layout exceeds one.*/ private void throwCustomException (int numOfChildViews) { if (numOfChildViews == 1) { throw new NumberOfFoldingLayoutChildrenException(FOLDING_VIEW_EXCEPTION_MESSAGE); } } public void setFoldListener(OnFoldListener foldListener) { mFoldListener = foldListener; } /** * Sets the fold factor of the folding view and updates all the corresponding * matrices and values to account for the new fold factor. Once that is complete, * it redraws itself with the new fold. */ public void setFoldFactor(float foldFactor) { if (foldFactor != mFoldFactor) { mFoldFactor = foldFactor; calculateMatrices(); invalidate(); } } public void setOrientation(Orientation orientation) { if (orientation != mOrientation) { mOrientation = orientation; updateFold(); } } public void setAnchorFactor(float anchorFactor) { if (anchorFactor != mAnchorFactor) { mAnchorFactor = anchorFactor; updateFold(); } } public void setNumberOfFolds(int numberOfFolds) { if (numberOfFolds != mNumberOfFolds) { mNumberOfFolds = numberOfFolds; updateFold(); } } public float getAnchorFactor() { return mAnchorFactor; } public Orientation getOrientation() { return mOrientation; } public float getFoldFactor() { return mFoldFactor; } public int getNumberOfFolds() { return mNumberOfFolds; } private void updateFold() { prepareFold(mOrientation, mAnchorFactor, mNumberOfFolds); calculateMatrices(); invalidate(); } /** * This method is called in order to update the fold's orientation, anchor * point and number of folds. This creates the necessary setup in order to * prepare the layout for a fold with the specified parameters. Some of the * dimensions required for the folding transformation are also acquired here. * * After this method is called, it will be in a completely unfolded state by default. */ private void prepareFold(Orientation orientation, float anchorFactor, int numberOfFolds) { mSrc = new float[NUM_OF_POLY_POINTS]; mDst = new float[NUM_OF_POLY_POINTS]; mDstRect = new Rect(); mFoldFactor = 0; mPreviousFoldFactor = 0; mIsFoldPrepared = false; mSolidShadow = new Paint(); mGradientShadow = new Paint(); mOrientation = orientation; mIsHorizontal = (orientation == Orientation.HORIZONTAL); if (mIsHorizontal) { mShadowLinearGradient = new LinearGradient(0, 0, SHADING_FACTOR, 0, Color.BLACK, Color.TRANSPARENT, TileMode.CLAMP); } else { mShadowLinearGradient = new LinearGradient(0, 0, 0, SHADING_FACTOR, Color.BLACK, Color.TRANSPARENT, TileMode.CLAMP); } mGradientShadow.setStyle(Style.FILL); mGradientShadow.setShader(mShadowLinearGradient); mShadowGradientMatrix = new Matrix(); mAnchorFactor = anchorFactor; mNumberOfFolds = numberOfFolds; mOriginalWidth = getMeasuredWidth(); mOriginalHeight = getMeasuredHeight(); mFoldRectArray = new Rect[mNumberOfFolds]; mMatrix = new Matrix [mNumberOfFolds]; for (int x = 0; x < mNumberOfFolds; x++) { mMatrix[x] = new Matrix(); } int h = mOriginalHeight; int w = mOriginalWidth; if (FoldingLayoutActivity.IS_JBMR2) { mFullBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(mFullBitmap); getChildAt(0).draw(canvas); } int delta = Math.round(mIsHorizontal ? ((float) w) / ((float) mNumberOfFolds) : ((float) h) /((float) mNumberOfFolds)); /* Loops through the number of folds and segments the full layout into a number * of smaller equal components. If the number of folds is odd, then one of the * components will be smaller than all the rest. Note that deltap below handles * the calculation for an odd number of folds.*/ for (int x = 0; x < mNumberOfFolds; x++) { if (mIsHorizontal) { int deltap = (x + 1) * delta > w ? w - x * delta : delta; mFoldRectArray[x] = new Rect(x * delta, 0, x * delta + deltap, h); } else { int deltap = (x + 1) * delta > h ? h - x * delta : delta; mFoldRectArray[x] = new Rect(0, x * delta, w, x * delta + deltap); } } if (mIsHorizontal) { mFoldMaxHeight = h; mFoldMaxWidth = delta; } else { mFoldMaxHeight = delta; mFoldMaxWidth = w; } mIsFoldPrepared = true; } /* * Calculates the transformation matrices used to draw each of the separate folding * segments from this view. */ private void calculateMatrices() { mShouldDraw = true; if (!mIsFoldPrepared) { return; } /** If the fold factor is 1 than the folding view should not be seen * and the canvas can be left completely empty. */ if (mFoldFactor == 1) { mShouldDraw = false; return; } if (mFoldFactor == 0 && mPreviousFoldFactor > 0) { mFoldListener.onEndFold(); } if (mPreviousFoldFactor == 0 && mFoldFactor > 0) { mFoldListener.onStartFold(); } mPreviousFoldFactor = mFoldFactor; /* Reset all the transformation matrices back to identity before computing * the new transformation */ for (int x = 0; x < mNumberOfFolds; x++) { mMatrix[x].reset(); } float cTranslationFactor = 1 - mFoldFactor; float translatedDistance = mIsHorizontal ? mOriginalWidth * cTranslationFactor : mOriginalHeight * cTranslationFactor; float translatedDistancePerFold = Math.round(translatedDistance / mNumberOfFolds); /* For an odd number of folds, the rounding error may cause the * translatedDistancePerFold to be grater than the max fold width or height. */ mFoldDrawWidth = mFoldMaxWidth < translatedDistancePerFold ? translatedDistancePerFold : mFoldMaxWidth; mFoldDrawHeight = mFoldMaxHeight < translatedDistancePerFold ? translatedDistancePerFold : mFoldMaxHeight; float translatedDistanceFoldSquared = translatedDistancePerFold * translatedDistancePerFold; /* Calculate the depth of the fold into the screen using pythagorean theorem. */ float depth = mIsHorizontal ? (float)Math.sqrt((double)(mFoldDrawWidth * mFoldDrawWidth - translatedDistanceFoldSquared)) : (float)Math.sqrt((double)(mFoldDrawHeight * mFoldDrawHeight - translatedDistanceFoldSquared)); /* The size of some object is always inversely proportional to the distance * it is away from the viewpoint. The constant can be varied to to affect the * amount of perspective. */ float scaleFactor = DEPTH_CONSTANT / (DEPTH_CONSTANT + depth); float scaledWidth, scaledHeight, bottomScaledPoint, topScaledPoint, rightScaledPoint, leftScaledPoint; if (mIsHorizontal) { scaledWidth = mFoldDrawWidth * cTranslationFactor; scaledHeight = mFoldDrawHeight * scaleFactor; } else { scaledWidth = mFoldDrawWidth * scaleFactor; scaledHeight = mFoldDrawHeight * cTranslationFactor; } topScaledPoint = (mFoldDrawHeight - scaledHeight) / 2.0f; bottomScaledPoint = topScaledPoint + scaledHeight; leftScaledPoint = (mFoldDrawWidth - scaledWidth) / 2.0f; rightScaledPoint = leftScaledPoint + scaledWidth; float anchorPoint = mIsHorizontal ? mAnchorFactor * mOriginalWidth : mAnchorFactor * mOriginalHeight; /* The fold along which the anchor point is located. */ float midFold = mIsHorizontal ? (anchorPoint / mFoldDrawWidth) : anchorPoint / mFoldDrawHeight; mSrc[0] = 0; mSrc[1] = 0; mSrc[2] = 0; mSrc[3] = mFoldDrawHeight; mSrc[4] = mFoldDrawWidth; mSrc[5] = 0; mSrc[6] = mFoldDrawWidth; mSrc[7] = mFoldDrawHeight; /* Computes the transformation matrix for each fold using the values calculated above. */ for (int x = 0; x < mNumberOfFolds; x++) { boolean isEven = (x % 2 == 0); if (mIsHorizontal) { mDst[0] = (anchorPoint > x * mFoldDrawWidth) ? anchorPoint + (x - midFold) * scaledWidth : anchorPoint - (midFold - x) * scaledWidth; mDst[1] = isEven ? 0 : topScaledPoint; mDst[2] = mDst[0]; mDst[3] = isEven ? mFoldDrawHeight: bottomScaledPoint; mDst[4] = (anchorPoint > (x + 1) * mFoldDrawWidth) ? anchorPoint + (x + 1 - midFold) * scaledWidth : anchorPoint - (midFold - x - 1) * scaledWidth; mDst[5] = isEven ? topScaledPoint : 0; mDst[6] = mDst[4]; mDst[7] = isEven ? bottomScaledPoint : mFoldDrawHeight; } else { mDst[0] = isEven ? 0 : leftScaledPoint; mDst[1] = (anchorPoint > x * mFoldDrawHeight) ? anchorPoint + (x - midFold) * scaledHeight : anchorPoint - (midFold - x) * scaledHeight; mDst[2] = isEven ? leftScaledPoint: 0; mDst[3] = (anchorPoint > (x + 1) * mFoldDrawHeight) ? anchorPoint + (x + 1 - midFold) * scaledHeight : anchorPoint - (midFold - x - 1) * scaledHeight; mDst[4] = isEven ? mFoldDrawWidth : rightScaledPoint; mDst[5] = mDst[1]; mDst[6] = isEven ? rightScaledPoint : mFoldDrawWidth; mDst[7] = mDst[3]; } /* Pixel fractions are present for odd number of folds which need to be * rounded off here.*/ for (int y = 0; y < 8; y ++) { mDst[y] = Math.round(mDst[y]); } /* If it so happens that any of the folds have reached a point where * the width or height of that fold is 0, then nothing needs to be * drawn onto the canvas because the view is essentially completely * folded.*/ if (mIsHorizontal) { if (mDst[4] <= mDst[0] || mDst[6] <= mDst[2]) { mShouldDraw = false; return; } } else { if (mDst[3] <= mDst[1] || mDst[7] <= mDst[5]) { mShouldDraw = false; return; } } /* Sets the shadow and bitmap transformation matrices.*/ mMatrix[x].setPolyToPoly(mSrc, 0, mDst, 0, NUM_OF_POLY_POINTS / 2); } /* The shadows on the folds are split into two parts: Solid shadows and gradients. * Every other fold has a solid shadow which overlays the whole fold. Similarly, * the folds in between these alternating folds also have an overlaying shadow. * However, it is a gradient that takes up part of the fold as opposed to a solid * shadow overlaying the whole fold.*/ /* Solid shadow paint object. */ int alpha = (int) (mFoldFactor * 255 * SHADING_ALPHA); mSolidShadow.setColor(Color.argb(alpha, 0, 0, 0)); if (mIsHorizontal) { mShadowGradientMatrix.setScale(mFoldDrawWidth, 1); mShadowLinearGradient.setLocalMatrix(mShadowGradientMatrix); } else { mShadowGradientMatrix.setScale(1, mFoldDrawHeight); mShadowLinearGradient.setLocalMatrix(mShadowGradientMatrix); } mGradientShadow.setAlpha(alpha); } @Override protected void dispatchDraw(Canvas canvas) { /** If prepareFold has not been called or if preparation has not completed yet, * then no custom drawing will take place so only need to invoke super's * onDraw and return. */ if (!mIsFoldPrepared || mFoldFactor == 0) { super.dispatchDraw(canvas); return; } if (!mShouldDraw) { return; } Rect src; /* Draws the bitmaps and shadows on the canvas with the appropriate transformations. */ for (int x = 0; x < mNumberOfFolds; x++) { src = mFoldRectArray[x]; /* The canvas is saved and restored for every individual fold*/ canvas.save(); /* Concatenates the canvas with the transformation matrix for the * the segment of the view corresponding to the actual image being * displayed. */ canvas.concat(mMatrix[x]); if (FoldingLayoutActivity.IS_JBMR2) { mDstRect.set(0, 0, src.width(), src.height()); canvas.drawBitmap(mFullBitmap, src, mDstRect, null); } else { /* The same transformation matrix is used for both the shadow and the image * segment. The canvas is clipped to account for the size of each fold and * is translated so they are drawn in the right place. The shadow is then drawn on * top of the different folds using the sametransformation matrix.*/ canvas.clipRect(0, 0, src.right - src.left, src.bottom - src.top); if (mIsHorizontal) { canvas.translate(-src.left, 0); } else { canvas.translate(0, -src.top); } super.dispatchDraw(canvas); if (mIsHorizontal) { canvas.translate(src.left, 0); } else { canvas.translate(0, src.top); } } /* Draws the shadows corresponding to this specific fold. */ if (x % 2 == 0) { canvas.drawRect(0, 0, mFoldDrawWidth, mFoldDrawHeight, mSolidShadow); } else { canvas.drawRect(0, 0, mFoldDrawWidth, mFoldDrawHeight, mGradientShadow); } canvas.restore(); } } } 在变量中。看到使用了Rect、Matrix、LinearGradient、Bitmap等graphics类,主要是负责画图。当然还有最基础的canvas和paint。在FoldingLayout中。大体能够分为三个部分,分别以三个方法为代表: 1、prepareFold(Orientation orientation, float anchorFactor, int numberOfFolds) 初始化准备。为变量赋值 2、calculateMatrices() 计算转换矩阵,用于绘制每个单独的折叠部分 3、dispatchDraw(Canvas canvas) 重载ViewGroup的方法,绘制图形 然后进入各个方法。在calculateMatrices()方法的最后,有一个以下的方法: /* Sets the shadow and bitmap transformation matrices.*/ mMatrix[x].setPolyToPoly(mSrc, 0, mDst, 0, NUM_OF_POLY_POINTS / 2); 这种方法是Matrix类里面的,那它是做什么用的呢?首先肯定是对矩阵进行变换,那会是什么效果呢?结合折叠的效果来说一下。在api中,这个类是这样描写叙述的, 这种方法就是设置指定的矩阵的src点映射到指定的dst点。这些点代表一个float数组,每一点由两个float值组成。比方一个矩形,用数组能够这样表示:[x0,y0,x1,y1,x2,y2,x3,y3],图示: 再来回想一下折叠效果是怎样实现的。能够把它拆分成例如以下图所看到的: 对于折叠一次的图形,能够把其分成两个部分,也就是先对图形进行分割,之后再对每个图形做拉伸变换,所以非常easy想到使用Matrix类实现。用的正是setPolyToPoly()方法。以下以一个实例说一下setPolyToPoly()方法的使用,代码例如以下: package com.example.demo; import android.support.v7.app.ActionBarActivity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.os.Bundle; import android.view.View; public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new MyView(this)); } class MyView extends View { private Bitmap bitmap; private Matrix mMatrix; public MyView(Context context) { super(context); bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a); mMatrix = new Matrix(); } @Override protected void onDraw(Canvas canvas) { float[] src = new float[] { 0, 0, // 左上 bitmap.getWidth(), 0,// 右上 bitmap.getWidth(), bitmap.getHeight(),// 右下 0, bitmap.getHeight() };// 左下 float[] dst = new float[] { 0, 0, bitmap.getWidth(), 50, bitmap.getWidth(), bitmap.getHeight() - 50, 0, bitmap.getHeight() }; mMatrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1); canvas.drawBitmap(bitmap, mMatrix, null); canvas.drawBitmap(bitmap, 0, bitmap.getHeight(), null); } } } 在数组中,设置的矩形四个顶点的坐标,在目的矩形的坐标中,改变了右上以及右下的坐标,使其分别向下和向上移动50像素。所以效果例如以下: 能够看到上图正是一个折叠效果(看图别晕。大自然是不是真的非常奇妙)。 FoldingLayout就是在这个基础之上延伸而来的,接下来就看看它是怎样具体实现的。 1、首先看一下 方法, /** * This method is called in order to update the fold's orientation, anchor * point and number of folds. This creates the necessary setup in order to * prepare the layout for a fold with the specified parameters. Some of the * dimensions required for the folding transformation are also acquired here. * * After this method is called, it will be in a completely unfolded state by default. */ private void prepareFold(Orientation orientation, float anchorFactor, int numberOfFolds) { //setPolyToPoly方法參数 mSrc = new float[NUM_OF_POLY_POINTS]; mDst = new float[NUM_OF_POLY_POINTS]; // mDstRect = new Rect(); mFoldFactor = 0; mPreviousFoldFactor = 0; //是否折叠 mIsFoldPrepared = false; //画笔 mSolidShadow = new Paint(); mGradientShadow = new Paint(); //方向 mOrientation = orientation; mIsHorizontal = (orientation == Orientation.HORIZONTAL); //折叠线附近的效果 if (mIsHorizontal) { mShadowLinearGradient = new LinearGradient(0, 0, SHADING_FACTOR, 0, Color.RED, Color.TRANSPARENT, TileMode.CLAMP); } else { mShadowLinearGradient = new LinearGradient(0, 0, 0, SHADING_FACTOR, Color.BLACK, Color.TRANSPARENT, TileMode.CLAMP); } //折叠线附近的效果 mGradientShadow.setStyle(Style.FILL); mGradientShadow.setShader(mShadowLinearGradient); mShadowGradientMatrix = new Matrix(); mAnchorFactor = anchorFactor; mNumberOfFolds = numberOfFolds; mOriginalWidth = getMeasuredWidth(); Log.i(" mOriginalWidth", mOriginalWidth+""); mOriginalHeight = getMeasuredHeight(); Log.i(" mOriginalHeight", mOriginalHeight+""); mFoldRectArray = new Rect[mNumberOfFolds]; mMatrix = new Matrix [mNumberOfFolds]; for (int x = 0; x < mNumberOfFolds; x++) { mMatrix[x] = new Matrix(); } int h = mOriginalHeight; int w = mOriginalWidth; if (FoldingLayoutActivity.IS_JBMR2) { mFullBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(mFullBitmap); getChildAt(0).draw(canvas); Log.i("IS_JBMR2", FoldingLayoutActivity.IS_JBMR2+""); } //折叠成几个部分,每隔部分的宽 int delta = Math.round(mIsHorizontal ? ((float) w) / ((float) mNumberOfFolds) : ((float) h) /((float) mNumberOfFolds)); Log.i("delta", delta+""); /* Loops through the number of folds and segments the full layout into a number * of smaller equal components. If the number of folds is odd, then one of the * components will be smaller than all the rest. Note that deltap below handles * the calculation for an odd number of folds.*/ for (int x = 0; x < mNumberOfFolds; x++) { if (mIsHorizontal) { int deltap = (x + 1) * delta > w ? w - x * delta : delta; Log.i("deltap", x+"x"+deltap+""); mFoldRectArray[x] = new Rect(x * delta, 0, x * delta + deltap, h); } else { int deltap = (x + 1) * delta > h ? h - x * delta : delta; mFoldRectArray[x] = new Rect(0, x * delta, w, x * delta + deltap); } } if (mIsHorizontal) { mFoldMaxHeight = h; mFoldMaxWidth = delta; } else { mFoldMaxHeight = delta; mFoldMaxWidth = w; } //设置已经准备就绪 mIsFoldPrepared = true; } 这种方法从名字就能够看出来是用于数据初始化。開始时初始化setPolyToPoly方法參数数组,然后设置了画笔、折叠方向、折叠线附近的折叠效果灯。 最重要的就是初始化这两个參数: private Rect[] mFoldRectArray: private Matrix [] mMatrix: 他们分别依据折叠的数目来赋值。 2、calculateMatrices() 这种方法是计算的核心部分。 /* * Calculates the transformation matrices used to draw each of the separate * folding segments from this view. */ private void calculateMatrices() { // 设置能够画图了 mShouldDraw = true; // 没有准备好 返回 if (! mIsFoldPrepared) { return; } /** * If the fold factor is 1 than the folding view should not be seen and * the canvas can be left completely empty. */ if ( mFoldFactor == 1) { // 已经全然折叠了不须要绘制了 mShouldDraw = false; return; } // 折叠结束 if ( mFoldFactor == 0 && mPreviousFoldFactor > 0) { mFoldListener.onEndFold(); } // 折叠開始 if ( mPreviousFoldFactor == 0 && mFoldFactor > 0) { mFoldListener.onStartFold(); } // 已经折叠了,将当前的折叠因子赋值到已经折叠过的折叠因子 mPreviousFoldFactor = mFoldFactor; /* * Reset all the transformation matrices back to identity before * computing the new transformation */ for ( int x = 0; x < mNumberOfFolds; x++) { mMatrix[x].reset(); } //依据折叠因子来得到相对系数 float cTranslationFactor = 1 - mFoldFactor; Log. i("cTranslationFactor" , cTranslationFactor + "" ); //计算总体变换的距离 float translatedDistance = mIsHorizontal ? mOriginalWidth * cTranslationFactor : mOriginalHeight * cTranslationFactor; Log. i("translatedDistance" , translatedDistance + "" ); //得到每个折叠视图变换的移动距离 float translatedDistancePerFold = Math.round(translatedDistance / mNumberOfFolds); Log. i("translatedDistancePerFold" , translatedDistancePerFold + "" ); /* * For an odd number of folds, the rounding error may cause the * translatedDistancePerFold to be grater than the max fold width or * height. */ Log. i("mFoldMaxWidth" , mFoldMaxWidth + "" ); //计算出被画视图的宽度 mFoldDrawWidth = mFoldMaxWidth < translatedDistancePerFold ? translatedDistancePerFold : mFoldMaxWidth; Log. i("mFoldDrawWidth" , mFoldDrawWidth + "" ); //计算出被画视图的高度 mFoldDrawHeight = mFoldMaxHeight < translatedDistancePerFold ? translatedDistancePerFold : mFoldMaxHeight; Log. i("mFoldDrawHeight" , mFoldDrawHeight + "" ); float translatedDistanceFoldSquared = translatedDistancePerFold * translatedDistancePerFold; Log. i("translatedDistanceFoldSquared" , translatedDistanceFoldSquared + ""); /* * Calculate the depth of the fold into the screen using pythagorean * theorem. * 依据勾股定理计算出折叠效果的深度 */ float depth = mIsHorizontal ? ( float) Math . sqrt((double) (mFoldDrawWidth * mFoldDrawWidth - translatedDistanceFoldSquared)) : ( float) Math . sqrt((double) (mFoldDrawHeight * mFoldDrawHeight - translatedDistanceFoldSquared)); Log. i("depth" , depth + "" ); /* * The size of some object is always inversely proportional to the * distance it is away from the viewpoint. The constant can be varied to * to affect the amount of perspective. * 这个地方又使用了一个因子,它是依据深度depth来得到的。主要也是用来计算相对的值,体如今高度上。 */ float scaleFactor = DEPTH_CONSTANT / ( DEPTH_CONSTANT + depth); Log. i("scaleFactor" , scaleFactor + "" ); float scaledWidth, scaledHeight, bottomScaledPoint, topScaledPoint, rightScaledPoint, leftScaledPoint; if ( mIsHorizontal) { //得到变化后的宽度,事实上和translatedDistance相差无几 scaledWidth = mFoldDrawWidth * cTranslationFactor; Log. i("scaledWidth" , scaledWidth + "" ); //得到变化后的高度 scaledHeight = mFoldDrawHeight * scaleFactor; Log. i("scaledHeight" , scaledHeight + "" ); } else { scaledWidth = mFoldDrawWidth * scaleFactor; scaledHeight = mFoldDrawHeight * cTranslationFactor; } //依据变化后的高度和宽度来计算出坐标点 topScaledPoint = ( mFoldDrawHeight - scaledHeight) / 2.0f; bottomScaledPoint = topScaledPoint + scaledHeight; leftScaledPoint = ( mFoldDrawWidth - scaledWidth) / 2.0f; rightScaledPoint = leftScaledPoint + scaledWidth; //锚点计算 float anchorPoint = mIsHorizontal ? mAnchorFactor * mOriginalWidth : mAnchorFactor * mOriginalHeight; Log. i("anchorPoint" , anchorPoint + "" ); /* The fold along which the anchor point is located. */ float midFold = mIsHorizontal ? (anchorPoint / mFoldDrawWidth) : anchorPoint / mFoldDrawHeight; //一下的坐标大家一定非常熟悉,就是之前介绍的方法的參数 表示单个折叠视图 mSrc[0] = 0; mSrc[1] = 0; mSrc[2] = 0; mSrc[3] = mFoldDrawHeight; mSrc[4] = mFoldDrawWidth; mSrc[5] = 0; mSrc[6] = mFoldDrawWidth; mSrc[7] = mFoldDrawHeight; /* * Computes the transformation matrix for each fold using the values * calculated above. */ for ( int x = 0; x < mNumberOfFolds; x++) { boolean isEven = (x % 2 == 0); if ( mIsHorizontal) { mDst[0] = (anchorPoint > x * mFoldDrawWidth) ? anchorPoint + (x - midFold) * scaledWidth : anchorPoint - (midFold - x) * scaledWidth; Log. i("mDst[0]" , mDst [0] + "" ); mDst[1] = isEven ? 0 : topScaledPoint; Log. i("mDst[1]" , mDst [1] + "" ); mDst[2] = mDst[0]; Log. i("mDst[2]" , mDst [2] + "" ); mDst[3] = isEven ? mFoldDrawHeight : bottomScaledPoint; Log. i("mDst[3]" , mDst [4] + "" ); mDst[4] = (anchorPoint > (x + 1) * mFoldDrawWidth) ? anchorPoint + (x + 1 - midFold) * scaledWidth : anchorPoint - (midFold - x - 1) * scaledWidth; Log. i("mDst[4]" , mDst [4] + "" ); mDst[5] = isEven ? topScaledPoint : 0; Log. i("mDst[5]" , mDst [5] + "" ); mDst[6] = mDst[4]; Log. i("mDst[6]" , mDst [6] + "" ); mDst[7] = isEven ? bottomScaledPoint : mFoldDrawHeight; Log. i("mDst[7]" , mDst [7] + "" ); } else { mDst[0] = isEven ? 0 : leftScaledPoint; mDst[1] = (anchorPoint > x * mFoldDrawHeight) ? anchorPoint + (x - midFold) * scaledHeight : anchorPoint - (midFold - x) * scaledHeight; mDst[2] = isEven ? leftScaledPoint : 0; mDst[3] = (anchorPoint > (x + 1) * mFoldDrawHeight) ? anchorPoint + (x + 1 - midFold) * scaledHeight : anchorPoint - (midFold - x - 1) * scaledHeight; mDst[4] = isEven ? mFoldDrawWidth : rightScaledPoint; mDst[5] = mDst[1]; mDst[6] = isEven ? rightScaledPoint : mFoldDrawWidth; mDst[7] = mDst[3]; } /* * Pixel fractions are present for odd number of folds which need to * be rounded off here. */ for ( int y = 0; y < 8; y++) { mDst[y] = Math. round(mDst[y]); } /* * If it so happens that any of the folds have reached a point where * the width or height of that fold is 0, then nothing needs to be * drawn onto the canvas because the view is essentially completely * folded. */ if ( mIsHorizontal) { if ( mDst[4] <= mDst[0] || mDst[6] <= mDst[2]) { mShouldDraw = false; return; } } else { if ( mDst[3] <= mDst[1] || mDst[7] <= mDst[5]) { mShouldDraw = false; return; } } /* Sets the shadow and bitmap transformation matrices. */ mMatrix[x].setPolyToPoly( mSrc, 0, mDst, 0, NUM_OF_POLY_POINTS / 2); } /* * The shadows on the folds are split into two parts: Solid shadows and * gradients. Every other fold has a solid shadow which overlays the * whole fold. Similarly, the folds in between these alternating folds * also have an overlaying shadow. However, it is a gradient that takes * up part of the fold as opposed to a solid shadow overlaying the whole * fold. */ /* Solid shadow paint object. */ int alpha = ( int) ( mFoldFactor * 255 * SHADING_ALPHA); mSolidShadow.setColor(Color. argb(alpha, 0, 0, 0)); if ( mIsHorizontal) { mShadowGradientMatrix.setScale( mFoldDrawWidth, 1); mShadowLinearGradient.setLocalMatrix( mShadowGradientMatrix); } else { mShadowGradientMatrix.setScale(1, mFoldDrawHeight); mShadowLinearGradient.setLocalMatrix( mShadowGradientMatrix); } mGradientShadow.setAlpha(alpha); } 首先依据mFoldFactor得到cTranslationFactor。mFoldFactor是依据手指滑动的距离得到的。是一个非常小的数值,假设滑动距离小的话每次仅仅有0.0几的变化。所以得到的cTranslationFactor就像是一个百分比一样,来计算相对的数值。然后依据cTranslationFactor计算出translatedDistance、mFoldDrawWidth、depth等。 具体说明已经在代码中凝视了,在此就不细说了,如图: 3、dispatchDraw(Canvas canvas) 最后就是把图形绘制出来。这种方法在之前的自己定义ViewGroup博客中已经提到过。假设不重写这种方法。那上面的工作室无法显示出来的。 看代码: @Override protected void dispatchDraw(Canvas canvas) { /** * If prepareFold has not been called or if preparation has not * completed yet, then no custom drawing will take place so only need to * invoke super's onDraw and return. */ if (! mIsFoldPrepared || mFoldFactor == 0) { super.dispatchDraw(canvas); return; } if (! mShouldDraw) { return; } Rect src; /* * Draws the bitmaps and shadows on the canvas with the appropriate * transformations. */ for ( int x = 0; x < mNumberOfFolds; x++) { src = mFoldRectArray[x]; /* The canvas is saved and restored for every individual fold */ canvas.save(); /* * Concatenates the canvas with the transformation matrix for the * the segment of the view corresponding to the actual image being * displayed. */ canvas.concat( mMatrix[x]); if (FoldingLayoutActivity. IS_JBMR2) { mDstRect.set(0, 0, src.width(), src.height()); canvas.drawBitmap( mFullBitmap, src, mDstRect, null); } else { /* * The same transformation matrix is used for both the shadow * and the image segment. The canvas is clipped to account for * the size of each fold and is translated so they are drawn in * the right place. The shadow is then drawn on top of the * different folds using the sametransformation matrix. */ canvas.clipRect(0, 0, src. right - src.left, src. bottom - src. top); if ( mIsHorizontal) { canvas.translate(-src. left, 0); } else { canvas.translate(0, -src. top); } super.dispatchDraw(canvas); if ( mIsHorizontal) { canvas.translate(src. left, 0); } else { canvas.translate(0, src. top); } } /* Draws the shadows corresponding to this specific fold. */ if (x % 2 == 0) { canvas.drawRect(0, 0, mFoldDrawWidth, mFoldDrawHeight, mSolidShadow); } else { canvas.drawRect(0, 0, mFoldDrawWidth, mFoldDrawHeight, mGradientShadow); } canvas.restore(); } } 事实上这种方法最基本的两个地方就是,canvas.concat( mMatrix[x])和canvas.clipRect(0, 0, src. right - src.left, src. bottom- src. top)。 首先在最外层是一个for循环。这个循环依据折叠的数目迭代,本例中这个数值为2,所以将迭代两次。接着从mFoldRectArray数组中取出一个Rect赋值给src。然后就调用canvas.concat( mMatrix[x])方法了。我们知道,这个Matrix刚才已经使用setPolyToPoly方法进行变换了。那怎样将变换后的矩阵的效果应用到视图上呢,就是使用canvas.concat( mMatrix[x])方法,假设没有这种方法。那就不会看到折叠的效果。然后就是canvas.clipRect(0, 0, src.right - src.left, src.bottom- src.top)方法了,这种方法用于对图形进行裁剪。显示出每个折叠的视图。须要注意的地方是这种方法要在兴许画画操作之前进行剪切才干生效,这也就是为什么这种方法在super.dispatchDraw(canvas)之前调用。接着还须要对视图进行平移操作,canvas.translate(-src.left, 0)。不然的话会显示两个同样的图片。 本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5147921.html,如需转载请自行联系原作者

资源下载

更多资源
Mario

Mario

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

Nacos

Nacos

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

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

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

用户登录
用户注册