一、类定义
+ (NSRunLoop *)currentRunLoop
如果调用的线程中没有runloop,那么将会创建一个并返回
+ (NSRunLoop *)mainRunLoop
返回主线程的runloop
- (void)acceptInputForMode:(NSString *)mode beforeDate:(NSDate *)limitDate
运行loop一次或者直到limitDate。如果没有input sources加入到这个loop,那么马上返回;否则一直运行到limitDate,或者接口到一个input source然后返回。
- (void)addPort:(NSPort *)aPort forMode:(NSString *)mode
- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
port和timer都可以添加到多个mode中
- (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(id)anArgument
取消所有mode中的perform select,argument必须跟指定调用时候的一样
- (void)cancelPerformSelectorsWithTarget:(id)target
- (NSString *)currentMode
如果run loop没有运行,那么返回nil
- (CFRunLoopRef)getCFRunLoop
- (NSDate *)limitDateForMode:(NSString *)mode
下一次运行的时间,如果没有指定的mode上没有input source,返回nil
- (void)performSelector:(SEL)aSelector target:(id)target argument:(id)anArgument order:(NSUInteger)order modes:(NSArray *)modes
order值越低优先级越高
- (void)removePort:(NSPort *)aPort forMode:(NSString *)mode
- (void)run
在default mode下无限运行loop,但是如果没有任何input source,会立即返回。手动移除所有已知的inout source并不能保证run loop停止运行,因为系统可能会添加一些input source。
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate
运行input source一次,为指定mode的input阻塞直到date的时间。如过没有input source,立即返回并返回NO。
- (void)runUntilDate:(NSDate *)limitDate
如果没有input source,立即返回。否则在limitDate到来之前,不停的循环。
再详细的就看文档吧
二、RunLoopMode
NSDefaultRunLoopMode 这是大多数操作中使用的模式。
NSConnectionReplyMode 该模式用来监控NSConnection对象。你通常不需要在你的代码中使用该模式。
NSModalPanelRunLoopMode Cocoa使用该模式来标识用于modal panel(模态面板)的事件。
NSEventTracking(UITrackingRunLoopMode) Cocoa使用该模式来处理用户界面相关的事件。
NSRunLoopCommonModes 这是一组可配置的通用模式。将input sources与该模式关联则同时也将input sources与该组中的其它模式进行了关联。对于Cocoa应用,该模式缺省的包含了default,modal以及event tracking模式。
一个常见的问题就是,主线程中一个NSTimer添加在default mode中,当界面上有一些scroll view的滚动频繁发生导致run loop运行在UItraking mode中,从而这个timer没能如期望那般的运行。所以,我们就可以把这个timer加到NSRunLoopCommonModes中来解决(iOS中)。
三.疑团重重
来看看这张经典的图片
其中Input source是一些异步的事件,比如port,selector等,这个会让runUntilDate:跳出(当然指的是非主线程中的runloop)。Timer source是同步的,一个timer结束后,在重复时间后或者手动fire后才会再一次调用。
在来看看这张图片
它说明了用户对ui的操作实际上是一种port,会放到一个队列中传到loop,然后由loop交给主线程处理。loop就是一个循环,接受event,传递,继续。主线程是另一个循环,负责事件的处理与界面的显示。当然这两者关系复杂。
在看下面的代码
-(void)press:(id)sender
{
[(UIButton*)sender setSelected:YES];
NSLog(@"begin"); // 1
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10.0]]; // 2
NSLog(@"end"); // 3
pageStillLoading = YES;
[NSThread detachNewThreadSelector:@selector(loadPageInBackground:)toTarget:self withObject:nil]; // 4
while (pageStillLoading) {
[[NSRunLoop currentRunLoop] runMode:UITrackingRunLoopMode beforeDate:[NSDate distantFuture]]; // 5
NSLog(@"while end");
}
NSLog(@"over");
}
-(void)loadPageInBackground:(id)sender
{
sleep(3); // 6
NSLog(@"timer"); // 7
pageStillLoading = NO; // 8
}
我在viewcontroller的view上加了一个uibutton,并且事件是press。touch up inside,然后,不在碰触界面(A)。发生了什么?
看看log:
2013-01-06 00:57:31.171 runloop[10146:c07] end
2013-01-06 00:57:34.173 runloop[10146:3703] timer
2013-01-06 00:58:00.001 runloop[10146:c07] while end
2013-01-06 00:58:00.002 runloop[10146:c07] over
pageStillLoading设置成NO之后过了近30s,while才结束。
如果我把5的mode改成NSRunLoopCommonModes或者在界面上在加一个按钮,然后不停的点击那个按钮(B),结果如下
2013-
本文同步发表于 Windows Phone论坛 WPDEVN :http://www.wpdevn.com/showtopic-138.aspx
今天在做 Wp8 中的ListBox 下拉刷新 发现一个蛋疼的问题 ManipulationCompleted 失效 这个事件死活不会触发 。。。
没有 MainpulationComplated 手势探测就很蛋疼了 (别告诉我用gestures的toolkit 那样做就恶心了。。。。 )
折腾了半天 发现 在ScrollViewer下的Border handler 了MainpulationCompleted 事件 (不知道这个是不是 控件的bug 。。。 囧)
那么既然 ScrollViewer 中拿不到事件 那我就再他下面去取事件吧 。。
直接上代码。。 具体的解释 在代码里面, 具体的说明自己看注释把。。。
{
public event EventHandler<ScrollEventArgs> OnScroll;
private ScrollContentPresenter _scrollContentPresenter;
private ScrollViewer _scrollViewer;
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.LayoutUpdated += (sender, e) =>
{
if (_scrollContentPresenter == null)
{
// 获取ListBox 中的 ListBox中的 ScrollViewer 以便获取当前滚动位置
_scrollViewer = FindChildOfType<ScrollViewer>(this.AssociatedObject,
obj =>
{
return true;
});
if (_scrollViewer == null) { return; }
//获取ScrollViewer 中的ContentPresenter 用来订阅ManipulationCompleted 事件,之所以不取Handle住事件Border 是考虑到 ScrollView 有可能被定制 里面就可能不止一个 Border
//而ContentPresenter 是在border 下面的 事件可以正常捕获 而且 在ScrollViwer 中 ContentPresenter 是唯一的。
_scrollContentPresenter = FindChildOfType<ScrollContentPresenter>(_scrollViewer,
obj =>
{
return true;
});
if (_scrollContentPresenter != null)
{
Logger.LogMessage("Event handler attched");
// 订阅事件 获取手势结束时的坐标
_scrollContentPresenter.ManipulationCompleted += OnManipulationCompleted;
// 订阅事件 获取手势开始时的坐标
// 注意:这里之所以不订阅 MainpulationStart事件 是因为 该事件给出的坐标是在点击位置 所在控件中的相对坐标
// 比如手势开始时点击位置是在ListBox 中 第三个ITem 中的一个图片上 那么给出坐标是这个图片中的相对坐标而非ScrollViewer的
// 而 ManipulationCopleted 给出的坐标是ScrollViewer的相对坐标 。。 坐标系不统一很蛋疼的。
_scrollContentPresenter.MouseLeftButtonDown += OnMouseLeftButtonDown;
}
}
};
base.OnAttached();
}
System.Windows.Input.MouseButtonEventArgs _buttonDownArgs;
void OnMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_buttonDownArgs = e;
}
void OnManipulationCompleted(object sender, System.Windows.Input.ManipulationCompletedEventArgs e)
{
double delta = e.ManipulationOrigin.Y - _buttonDownArgs.GetPosition(e.ManipulationContainer).Y;/这就是通过MouseLeftButtonDown 获取到坐标的好处,直接统一了坐标系:)
//下面就就是判断手势位置什么的了 我就不解释。。。 ScrollEventArgs 这些我自己定义的参数类 大家就不要在意啦。。。
if ( _scrollViewer.ScrollableHeight == _scrollViewer.ScrollableHeight && delta <-100)
{
FireOnScroll(new ScrollEventArgs(ScrollState.Bottom));
}
else if (_scrollViewer.VerticalOffset ==0 && delta > 100)
{
FireOnScroll(new ScrollEventArgs(ScrollState.Top));
}
}
void FireOnScroll(ScrollEventArgs args)
{
if (OnScroll != null)
{
OnScroll(_scrollViewer, args);
}
}
static T FindChildOfType<T>(DependencyObject root, Func<T, bool> verifyFunc) where T : class
{
var queue = new Queue<DependencyObject>();
queue.Enqueue(root);
while (queue.Count > 0)
{
DependencyObject current = queue.Dequeue();
for (int i = VisualTreeHelper.GetChildrenCount(current) - 1; 0 <= i; i--)
{
var child = VisualTreeHelper.GetChild(current, i);
var typedChild = child as T;
if (typedChild != null)
{
if (verifyFunc(typedChild))
return typedChild;
}
queue.Enqueue(child);
虽说Android 系统给我们提供了RadioButton但是为了我们的应用有种"与众不同"的效果,因为android的太死板太斯通见惯了.往往都会定制自己的图标.下面我给大家介绍一下我实现的方法:
方法:运用组合控件(ImageView and TextView)
组合控件代码: /***
* 组合控件
*
* @author zhangjia
*
*/
public class RadioButton extends LinearLayout {
private Context context;
private ImageView imageView;
private TextView textView;
private int index = 0;
private int id = 0;// 判断是否选中
private RadioButton tempRadioButton;// 模版用于保存上次点击的对象
private int state[] = { R.drawable.radio_unchecked,
R.drawable.radio_checked };
/***
* 改变图片
*/
public void ChageImage() {
index++;
id = index % 2;// 获取图片id
imageView.setImageResource(state[id]);
}
/***
* 设置文本
*
* @param text
*/
public void setText(String text) {
textView.setText(text);
}
public String getText() {
return id == 0 ? "" : textView.getText().toString();
}
public RadioButton(Context context) {
this(context, null);
}
public RadioButton(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
LayoutInflater.from(context).inflate(R.layout.item, this, true);
imageView = (ImageView) findViewById(R.id.iv_item);
textView = (TextView) findViewById(R.id.tv_item);
}
}
上面的实现的很容易,所以不过多解释.
下面是调用代码:
public class MainActivity extends Activity {
ListView listView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
listView = (ListView) findViewById(R.id.lv_main);
listView.setAdapter(new MyAdapter(this));
}
/***
* @author jia
*/
RadioButton temp;
class MyAdapter extends BaseAdapter {
private Context context;
private LayoutInflater inflater;
public MyAdapter(Context context) {
super();
this.context = context;
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return 10;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final RadioButton radioButton;
if (convertView == null) {
radioButton = new RadioButton(context);
} else {
radioButton = (RadioButton) convertView;
}
radioButton.setText(position + "");
radioButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 模版不为空,则chage.
if (temp != null) {
temp.ChageImage();
}
temp = radioButton;
radioButton.ChageImage();
Toast.makeText(context, radioButton.getText(), 1000).show();
}
});
return radioButton;
}
}
}我来说明一下:我们首先创建一个temp模版,用于记忆你点击的那个RadioButton对象. 在你点击时候,首先查看temp是否为null,如果不为空则执行 temp.ChageImage(); 这个方法是取消选中效果.如果不为null,则首先对该RadioButton执行,取消该按钮选中状态.在执行你点击的那个RadioButton的ChageImage方法,最后记得要把当前的RadioButton付给temp.
效果:
效果是实现了,不过有个小问题,因为目前只有10条数据是看不出效果的.换成20条你就会发现很诡异的问题。
图“:
第15条数据会自动勾选上,找了又找,最后终于发现了,是因为listview 的问题。看下面:
final RadioButton radioButton;
if (convertView == null) {
radioButton = new RadioButton(context);
} else {
radioButton = (RadioButton) convertView;
}
也许你会发现了,因为我们为了提高效率,重用了listview个convertView.所以会出现这种bug,解决方法也很简单,只需要我们把上面代码更换为
final RadioButton radioButton;
radioButton = new RadioButton(context);
虽说这样效率有点低,但是有时候我们需要则断一下,只要能实现效果,偶尔对性能放下水也是OK的,何况这种情况下不可能有那么多列.
项目实现样式:
看起来还凑合吧。
这里我把代码上传一下,不足的地方,自己可以进行调整,我只是提供个思路.
源码下载
[color=ize:18px]
[color=ize:18px]额外拓展:
[color=ize:18px]/*****************************************************************************/
[color=ize:18px]LayoutInflater.from(context).inflate(R.layout.item, this);
[color=ize:18px]View view=LayoutInflater.from(context).inflate(R.