Reputation: 587
My problem is that when I focus on the textfield, the keyboard appears but the layout doesn't resize to keep it in the view. This problem is only with android and not iOS: Here a screenshot of what I mean:
Before and after on android:
Before and after on iOS:
Here is my class :
class PlayerSelectionPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
int itemCount = Provider.of<PlayerProvider>(context).getPlayerList.length;
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).translate('player_selection_page_title')),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
HapticFeedback.mediumImpact();
Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage(), settings: RouteSettings(name: 'Home page')));
},
child: Icon(
Icons.chevron_right,
size: 30,
color: Colors.white,
),
),
body: itemCount > 0 ? ListView.builder(
itemCount: itemCount,
itemBuilder: (context, index) {
return Column(
children: [
PlayerDismissible(index),
Divider(
height: 0,
)
],
);
}) : Container(
padding: EdgeInsets.all(20),
alignment: Alignment.topCenter,
child: Text(AppLocalizations.of(context).translate('player_selection_page_empty_text'), textAlign: TextAlign.center, style: Theme.of(context).textTheme.subtitle2)
),
bottomSheet: BottomPlayerBar(),
);
}
}
And here is my BottomPlayerBar() :
class BottomPlayerBar extends StatefulWidget{
@override
_BottomPlayerBarState createState() => _BottomPlayerBarState();
}
class _BottomPlayerBarState extends State<BottomPlayerBar> {
String playerName;
FocusNode myFocusNode;
@override
void initState() {
super.initState();
myFocusNode = FocusNode();
SchedulerBinding.instance.addPostFrameCallback((_) {
if (ModalRoute.of(context).isCurrent) {
myFocusNode.requestFocus();
}
});
}
@override
Widget build(BuildContext context) {
return Container(
height: 80, color: Theme.of(context).primaryColor,
padding: EdgeInsets.only(top: 20, bottom: 25, left: 20, right: 70),
child: TextField(
focusNode: myFocusNode,
textCapitalization: TextCapitalization.words,
onChanged: (val) => playerName = val.trim(),
onSubmitted: (val) {
if (playerName != null && playerName != '') {
Provider.of<PlayerProvider>(context, listen: false).addPlayer(playerName);
HapticFeedback.lightImpact();
myFocusNode.requestFocus();
} else {
myFocusNode.unfocus();
}
},
maxLength: 19,
autocorrect: false,
decoration: new InputDecoration(
counterText: "",
border: new OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: const BorderRadius.all(
const Radius.circular(30.0),
),
),
filled: true,
contentPadding: EdgeInsets.symmetric(vertical: 0, horizontal: 20),
hintStyle: GoogleFonts.rubik(color: Colors.grey[500], fontWeight: FontWeight.bold),
hintText: AppLocalizations.of(context).translate('player_selection_page_hint'),
fillColor: Colors.white),
)
);
}
@override
void dispose() {
super.dispose();
myFocusNode.dispose();
}
}
Here is my android manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="myApp">
<application
android:name="io.flutter.app.FlutterApplication"
android:label="myApp !"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.LaunchTheme"
android:resource="@style/LaunchTheme"
/>
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
Upvotes: 5
Views: 5495
Reputation: 276
Following the app comments for me, I added a statefulBuilder, so the bottom sheet gets informed of the padding changes
showModalBottomSheet(
backgroundColor: Colors.transparent,
barrierColor: Colors.black.withOpacity(0.2),
elevation: 0.0,
context: context,
isScrollControlled: true,
builder: (_) {
return StatefulBuilder(builder: (context, StateSetter stateSetter) {
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: Column(
children: [
TextField(),
TextField(),
TextField(),
TextField(),
],
),
);
});
},
);
Upvotes: 0
Reputation: 7105
android/app/src/main/res/values/styles.xml
For me changing below item property from true to false
<item name="android:windowFullscreen">false</item>
I had this issue, too. This line was added as a result of using the flutter_native_splash package.
Upvotes: 1
Reputation: 5408
Resizing the page when the soft keyboard appears is handled through the android:windowSoftInputMode
activity parameter in AndroidManifest. See the official documnetation
If you have android:windowSoftInputMode="adjustResize"
in your AndroidManifest.xml, the textfield will automatically reposition when the softkeyboard appears. The same behaviour will not happen if the parameter is not there in AndroidManifest.xml
Could you recheck this by modifying your AndroidManifest. That will also explain why it is not working only in Android but works in iOS (Because iOS handles this implicitly)
P.S: When modifying the AndoridManifest.xml, hot reload may not work. You will need an actual stop and start running the app.
Upvotes: 1
Reputation: 169
You can wrap your BottomPlayerBar widget with the Transform Widget as shown below.
Transform.translate(
offset: Offset(0.0, -1 * MediaQuery.of(context).viewInsets.bottom),
child: BottomPlayerBar(),
);
Upvotes: 7
Reputation: 552
Use margin for your BottomPlayerBar widget:
margin: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
Upvotes: 1
Reputation: 220
You can use a stack
like this :
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Column(
children: [
Flexible(
child: ListView.builder(itemBuilder: null),
),
Container(
width: double.infinity,
height: 100,
child: TextField(),
)
],
)
],
),
);
}
Upvotes: 0
Reputation: 306
A simple solution might be to use the bottom padding for you BottomPlayerBar,
Instead of,
padding: EdgeInsets.only(top: 20, bottom: 25, left: 20, right: 70),
Try,
padding: EdgeInsets.only(top: 20, bottom: 25 + MediaQuery.of(context).padding.bottom, left: 20, right: 70),
Here we are just adding any padding added to the bottom of the scaffold from
MediaQuery.of(context).padding.bottom
Upvotes: 0
Reputation: 5788
So that's why I don't usually try to use Scaffold
build in parameters. To save time on resolving platform dependent issues I would go with using simple Widgets
to achieve result you want.
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
// YOU BODY VIEW 85 HEIGHT %
ConstrainedBox(
constraints: BoxConstraints(
minWidth: MediaQuery.of(context).size.width,
minHeight: MediaQuery.of(context).size.height * 0.85,
),
child: MainWidget()
),
// YOUR BOTTOM VIEW 15 %
ConstrainedBox(
constraints: BoxConstraints(
minWidth: MediaQuery.of(context).size.width,
minHeight: MediaQuery.of(context).size.height * 0.15,
),
child: BottomPlayerBar()
),
],
),
),
);
}
For your example above PlayerSelectionPage
.
class PlayerSelectionPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
int itemCount = Provider.of<PlayerProvider>(context).getPlayerList.length;
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).translate('player_selection_page_title')),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
HapticFeedback.mediumImpact();
Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage(), settings: RouteSettings(name: 'Home page')));
},
child: Icon(
Icons.chevron_right,
size: 30,
color: Colors.white,
),
),
body: SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
// NEW VIEW
ConstrainedBox(
constraints: BoxConstraints(
minWidth: MediaQuery.of(context).size.width,
minHeight: MediaQuery.of(context).size.height * 0.85,
),
child: itemCount > 0 ? ListView.builder(
itemCount: itemCount,
itemBuilder: (context, index) {
return Column(
children: [
PlayerDismissible(index),
Divider(
height: 0,
)
],
);
}) : Container(
padding: EdgeInsets.all(20),
alignment: Alignment.topCenter,
child: Text(AppLocalizations.of(context).translate('player_selection_page_empty_text'), textAlign: TextAlign.center, style: Theme.of(context).textTheme.subtitle2)
),
),
// NEW VIEW
ConstrainedBox(
constraints: BoxConstraints(
minWidth: MediaQuery.of(context).size.width,
minHeight: MediaQuery.of(context).size.height * 0.15,
),
child: BottomPlayerBar()
),
],
),
),
body: itemCount > 0 ? ListView.builder(
itemCount: itemCount,
itemBuilder: (context, index) {
return Column(
children: [
PlayerDismissible(index),
Divider(
height: 0,
)
],
);
}) : Container(
padding: EdgeInsets.all(20),
alignment: Alignment.topCenter,
child: Text(AppLocalizations.of(context).translate('player_selection_page_empty_text'), textAlign: TextAlign.center, style: Theme.of(context).textTheme.subtitle2)
),
);
}
}
Upvotes: 0
Reputation: 482
you can add isScrollControlled = true to showModalBottomSheet it'll allow the bottom sheet to take the full required height which gives more insurance that TextField is not covered by the keyboard.
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(25.0))),
backgroundColor: Colors.black,
context: context,
isScrollControlled: true,
builder: (context) => Padding(
padding: const EdgeInsets.symmetric(horizontal:18 ),
child: BottomPlayerBar(),
),
));
Upvotes: 0