2018年9月15日土曜日

AlertDialog の Button の有効/無効を切り替える

AlertDialog の PositiveButton, NegativeButton, NeutralButton では、listener での実装によらずタップしたときに必ずダイアログが閉じます。

例えば AlertDialog でテキストを入力するようにして、未入力のときはボタンを押せないようにしたいとします。

AlertDialog の getButton() で Button インスタンスが取れるのでこれを利用します。
取得するボタンは BUTTON_POSITIVE, BUTTON_NEGATIVE, BUTTON_NEUTRAL で指定します。

あとは EditText に TextWatcher を追加して、テキストの変更時にボタンの isEnabled を変更します。

初回時 EditText が空ならボタンを disabled にしておかないといけません。
AlertDialog の getButton() は show() の前に呼ぶと NPE になるので注意が必要です。 val editText = EditText(this).apply { inputType = InputType.TYPE_CLASS_TEXT layoutParams = FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT ).apply { val margin = (16 * resources.displayMetrics.density).toInt() marginStart = margin marginEnd = margin } } val frameLayout = FrameLayout(this).apply { addView(editText) } val dialog = AlertDialog.Builder(this) .setTitle("Title") .setMessage("Message") .setView(frameLayout) .setPositiveButton(android.R.string.ok, null) .create() editText.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(s: Editable?) { } override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = !s.isNullOrBlank() } }) dialog.show() dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false



Android Activity Transitions の xml 定義で exclude を指定する

コードでのやりかたは「Android Activity Transitions の対象から、Navigation Bar と Status Bar を外す(Activity Transitions を実装する その2)」に書きました。

xml で transition を定義する場合は以下のように targets タグと target タグを使います。 <?xml version="1.0" encoding="utf-8"?> <slide xmlns:android="http://schemas.android.com/apk/res/android" android:slideEdge="end"> <targets> <target android:excludeId="@android:id/navigationBarBackground" /> <target android:excludeId="@android:id/statusBarBackground" /> </targets> </slide> target タグには excludeId の他にも以下の属性を指定できます。
  • android:targetClass
  • android:targetId
  • android:excludeId
  • android:excludeClass
  • android:targetName
  • android:excludeName


2018年9月14日金曜日

android:windowCloseOnTouchOutside を指定するとどうなるのか

android:windowCloseOnTouchOutside は API Level 11 で追加されたテーマ用の属性で、true を指定すると、Dialog 系の theme を指定した Activity でダイアログ(というか window)以外の領域をタップしたときにダイアログが閉じます(というか Activity が finish() します)。

この属性は Window に関するもので、Window では以下のフィールドとメソッドが関連します。

Window public abstract class Window { ... private boolean mCloseOnTouchOutside = false; private boolean mSetCloseOnTouchOutside = false; ... /** @hide */ public void setCloseOnTouchOutside(boolean close) { mCloseOnTouchOutside = close; mSetCloseOnTouchOutside = true; } /** @hide */ public void setCloseOnTouchOutsideIfNotSet(boolean close) { if (!mSetCloseOnTouchOutside) { mCloseOnTouchOutside = close; mSetCloseOnTouchOutside = true; } } ... /** @hide */ public boolean shouldCloseOnTouch(Context context, MotionEvent event) { final boolean isOutside = event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event) || event.getAction() == MotionEvent.ACTION_OUTSIDE; if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) { return true; } return false; } private boolean isOutOfBounds(Context context, MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop(); final View decorView = getDecorView(); return (x < -slop) || (y < -slop) || (x > (decorView.getWidth()+slop)) || (y > (decorView.getHeight()+slop)); } } Window の shouldCloseOnTouch() では、mCloseOnTouchOutside が true、かつ peekDecorView が null ではない、かつ MotionEvent の action が MotionEvent.ACTION_OUTSIDE、もしくは MotionEvent.ACTION_DOWN でタップ位置が DecorView の外側の場合、true が返ります。

Window の setCloseOnTouchOutside() は hide になっていて通常のアプリからは呼べません。 ではコードでは指定できないのかというと、Activity の setFinishOnTouchOutside() から指定できます。

Activity public void setFinishOnTouchOutside(boolean finish) { mWindow.setCloseOnTouchOutside(finish); } Activity の onTouchEvent() で Window の shouldCloseOnTouch() を呼んでおり、これにより DecorView の外側をタップすると Activity が finish() します。

Activity public boolean onTouchEvent(MotionEvent event) { if (mWindow.shouldCloseOnTouch(this, event)) { finish(); return true; } return false; }

ちなみに android:windowCloseOnTouchOutside 属性の設定値は、PhoneWindow から Window.setCloseOnTouchOutsideIfNotSet() を呼ぶことで適用されています。



2018年9月7日金曜日

Android Activity Transitions の対象をグループ化する

ActivityTransition に Slide を指定すると、デフォルトでは View ごとに別々にアニメーションします。

例えばボタンを縦に並べた Activity に Slide で enter すると、スライド中はボタンの間隔が広がって、徐々に詰まっていきます。

左 → 右 : スライド中


ボタン同士の配置そのままに Slide させるには、ボタンの親の ViewGroup の setTransitionGroup() で true をセットします。 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) button.setOnClickListener { startActivity( Intent(this, MainActivity2::class.java), ActivityOptions.makeSceneTransitionAnimation(this).toBundle() ) } } } class MainActivity2 : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS) window.enterTransition = Slide() window.exitTransition = Slide() setContentView(R.layout.activity_main2) // これを追加 constraintLayout.isTransitionGroup = true } }

左 → 右 : スライド中



解説

ViewGroup の captureTransitioningViews() で isTransitionGroup() が false の場合、各子 View の captureTransitioningViews() を呼びます。 View の captureTransitioningViews() では VISIBLE な場合自身を transition するリストに追加します。

つまり、ViewGroup の setTransitionGroup() で true をセットすると、その ViewGroup が transition するリストに追加され、子 View はされなくなります。 public abstract class ViewGroup ... { ... /** @hide */ @Override public void captureTransitioningViews(List<View> transitioningViews) { if (getVisibility() != View.VISIBLE) { return; } if (isTransitionGroup()) { transitioningViews.add(this); } else { int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); child.captureTransitioningViews(transitioningViews); } } } ... } public class View ...{ ... public void captureTransitioningViews(List<View> transitioningViews) { if (getVisibility() == View.VISIBLE) { transitioningViews.add(this); } } ... }


'},ClipboardSwf:null,Version:'1.5.1'}};dp.SyntaxHighlighter=dp.sh;dp.sh.Toolbar.Commands={ExpandSource:{label:'+ expand source',check:function(highlighter){return highlighter.collapse;},func:function(sender,highlighter) {sender.parentNode.removeChild(sender);highlighter.div.className=highlighter.div.className.replace('collapsed','');}},ViewSource:{label:'view plain',func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/'+code+'');wnd.document.close();}},CopyToClipboard:{label:'copy to clipboard',check:function(){return window.clipboardData!=null||dp.sh.ClipboardSwf!=null;},func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&');if(window.clipboardData) {window.clipboardData.setData('text',code);} else if(dp.sh.ClipboardSwf!=null) {var flashcopier=highlighter.flashCopier;if(flashcopier==null) {flashcopier=document.createElement('div');highlighter.flashCopier=flashcopier;highlighter.div.appendChild(flashcopier);} flashcopier.innerHTML='';} alert('The code is in your clipboard now');}},PrintSource:{label:'print',func:function(sender,highlighter) {var iframe=document.createElement('IFRAME');var doc=null;iframe.style.cssText='position:absolute;width:0px;height:0px;left:-500px;top:-500px;';document.body.appendChild(iframe);doc=iframe.contentWindow.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write('

'+highlighter.div.innerHTML+'

');doc.close();iframe.contentWindow.focus();iframe.contentWindow.print();alert('Printing...');document.body.removeChild(iframe);}},About:{label:'?',func:function(highlighter) {var wnd=window.open('','_blank','dialog,width=300,height=150,scrollbars=0');var doc=wnd.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write(dp.sh.Strings.AboutDialog.replace('{V}',dp.sh.Version));doc.close();wnd.focus();}}};dp.sh.Toolbar.Create=function(highlighter) {var div=document.createElement('DIV');div.className='tools';for(var name in dp.sh.Toolbar.Commands) {var cmd=dp.sh.Toolbar.Commands[name];if(cmd.check!=null&&!cmd.check(highlighter)) continue;div.innerHTML+=''+cmd.label+'';} return div;} dp.sh.Toolbar.Command=function(name,sender) {var n=sender;while(n!=null&&n.className.indexOf('dp-highlighter')==-1) n=n.parentNode;if(n!=null) dp.sh.Toolbar.Commands[name].func(sender,n.highlighter);} dp.sh.Utils.CopyStyles=function(destDoc,sourceDoc) {var links=sourceDoc.getElementsByTagName('link');for(var i=0;i');} dp.sh.Utils.FixForBlogger=function(str) {return(dp.sh.isBloggerMode==true)?str.replace(/
|<br\s*\/?>/gi,''):str;} dp.sh.RegexLib={MultiLineCComments:new RegExp('/\\*[\\s\\S]*?\\*/','gm'),SingleLineCComments:new RegExp('//.*$','gm'),SingleLinePerlComments:new RegExp('#.*$','gm'),DoubleQuotedString:new RegExp('"(?:\\.|(\\\\\\")|[^\\""\\n])*"','g'),SingleQuotedString:new RegExp("'(?:\\.|(\\\\\\')|[^\\''\\n])*'",'g')};dp.sh.Match=function(value,index,css) {this.value=value;this.index=index;this.length=value.length;this.css=css;} dp.sh.Highlighter=function() {this.noGutter=false;this.addControls=true;this.collapse=false;this.tabsToSpaces=true;this.wrapColumn=80;this.showColumns=true;} dp.sh.Highlighter.SortCallback=function(m1,m2) {if(m1.indexm2.index) return 1;else {if(m1.lengthm2.length) return 1;} return 0;} dp.sh.Highlighter.prototype.CreateElement=function(name) {var result=document.createElement(name);result.highlighter=this;return result;} dp.sh.Highlighter.prototype.GetMatches=function(regex,css) {var index=0;var match=null;while((match=regex.exec(this.code))!=null) this.matches[this.matches.length]=new dp.sh.Match(match[0],match.index,css);} dp.sh.Highlighter.prototype.AddBit=function(str,css) {if(str==null||str.length==0) return;var span=this.CreateElement('SPAN');str=str.replace(/ /g,' ');str=str.replace(/');if(css!=null) {if((/br/gi).test(str)) {var lines=str.split(' 
');for(var i=0;ic.index)&&(match.index/gi,'\n');var lines=html.split('\n');if(this.addControls==true) this.bar.appendChild(dp.sh.Toolbar.Create(this));if(this.showColumns) {var div=this.CreateElement('div');var columns=this.CreateElement('div');var showEvery=10;var i=1;while(i<=150) {if(i%showEvery==0) {div.innerHTML+=i;i+=(i+'').length;} else {div.innerHTML+='·';i++;}} columns.className='columns';columns.appendChild(div);this.bar.appendChild(columns);} for(var i=0,lineIndex=this.firstLine;i0;i++) {if(Trim(lines[i]).length==0) continue;var matches=regex.exec(lines[i]);if(matches!=null&&matches.length>0) min=Math.min(matches[0].length,min);} if(min>0) for(var i=0;i

Blogger Syntax Highliter

Version: {V}

http://www.dreamprojections.com/syntaxhighlighter

©2004-2007 Alex Gorbatchev.

'},ClipboardSwf:null,Version:'1.5.1'}};dp.SyntaxHighlighter=dp.sh;dp.sh.Toolbar.Commands={ExpandSource:{label:'+ expand source',check:function(highlighter){return highlighter.collapse;},func:function(sender,highlighter) {sender.parentNode.removeChild(sender);highlighter.div.className=highlighter.div.className.replace('collapsed','');}},ViewSource:{label:'view plain',func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/'+code+'');wnd.document.close();}},CopyToClipboard:{label:'copy to clipboard',check:function(){return window.clipboardData!=null||dp.sh.ClipboardSwf!=null;},func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&');if(window.clipboardData) {window.clipboardData.setData('text',code);} else if(dp.sh.ClipboardSwf!=null) {var flashcopier=highlighter.flashCopier;if(flashcopier==null) {flashcopier=document.createElement('div');highlighter.flashCopier=flashcopier;highlighter.div.appendChild(flashcopier);} flashcopier.innerHTML='';} alert('The code is in your clipboard now');}},PrintSource:{label:'print',func:function(sender,highlighter) {var iframe=document.createElement('IFRAME');var doc=null;iframe.style.cssText='position:absolute;width:0px;height:0px;left:-500px;top:-500px;';document.body.appendChild(iframe);doc=iframe.contentWindow.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write('

'+highlighter.div.innerHTML+'

');doc.close();iframe.contentWindow.focus();iframe.contentWindow.print();alert('Printing...');document.body.removeChild(iframe);}},About:{label:'?',func:function(highlighter) {var wnd=window.open('','_blank','dialog,width=300,height=150,scrollbars=0');var doc=wnd.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write(dp.sh.Strings.AboutDialog.replace('{V}',dp.sh.Version));doc.close();wnd.focus();}}};dp.sh.Toolbar.Create=function(highlighter) {var div=document.createElement('DIV');div.className='tools';for(var name in dp.sh.Toolbar.Commands) {var cmd=dp.sh.Toolbar.Commands[name];if(cmd.check!=null&&!cmd.check(highlighter)) continue;div.innerHTML+=''+cmd.label+'';} return div;} dp.sh.Toolbar.Command=function(name,sender) {var n=sender;while(n!=null&&n.className.indexOf('dp-highlighter')==-1) n=n.parentNode;if(n!=null) dp.sh.Toolbar.Commands[name].func(sender,n.highlighter);} dp.sh.Utils.CopyStyles=function(destDoc,sourceDoc) {var links=sourceDoc.getElementsByTagName('link');for(var i=0;i');} dp.sh.Utils.FixForBlogger=function(str) {return(dp.sh.isBloggerMode==true)?str.replace(/
|<br\s*\/?>/gi,'\n'):str;} dp.sh.RegexLib={MultiLineCComments:new RegExp('/\\*[\\s\\S]*?\\*/','gm'),SingleLineCComments:new RegExp('//.*$','gm'),SingleLinePerlComments:new RegExp('#.*$','gm'),DoubleQuotedString:new RegExp('"(?:\\.|(\\\\\\")|[^\\""\\n])*"','g'),SingleQuotedString:new RegExp("'(?:\\.|(\\\\\\')|[^\\''\\n])*'",'g')};dp.sh.Match=function(value,index,css) {this.value=value;this.index=index;this.length=value.length;this.css=css;} dp.sh.Highlighter=function() {this.noGutter=false;this.addControls=true;this.collapse=false;this.tabsToSpaces=true;this.wrapColumn=80;this.showColumns=true;} dp.sh.Highlighter.SortCallback=function(m1,m2) {if(m1.indexm2.index) return 1;else {if(m1.lengthm2.length) return 1;} return 0;} dp.sh.Highlighter.prototype.CreateElement=function(name) {var result=document.createElement(name);result.highlighter=this;return result;} dp.sh.Highlighter.prototype.GetMatches=function(regex,css) {var index=0;var match=null;while((match=regex.exec(this.code))!=null) this.matches[this.matches.length]=new dp.sh.Match(match[0],match.index,css);} dp.sh.Highlighter.prototype.AddBit=function(str,css) {if(str==null||str.length==0) return;var span=this.CreateElement('SPAN');str=str.replace(/ /g,' ');str=str.replace(/');if(css!=null) {if((/br/gi).test(str)) {var lines=str.split(' 
');for(var i=0;ic.index)&&(match.index/gi,'\n');var lines=html.split('\n');if(this.addControls==true) this.bar.appendChild(dp.sh.Toolbar.Create(this));if(this.showColumns) {var div=this.CreateElement('div');var columns=this.CreateElement('div');var showEvery=10;var i=1;while(i<=150) {if(i%showEvery==0) {div.innerHTML+=i;i+=(i+'').length;} else {div.innerHTML+='·';i++;}} columns.className='columns';columns.appendChild(div);this.bar.appendChild(columns);} for(var i=0,lineIndex=this.firstLine;i0;i++) {if(Trim(lines[i]).length==0) continue;var matches=regex.exec(lines[i]);if(matches!=null&&matches.length>0) min=Math.min(matches[0].length,min);} if(min>0) for(var i=0;i

ページビューの合計