1672884972
URLImage
is a SwiftUI view that displays an image downloaded from provided URL. URLImage
manages downloading remote image and caching it locally, both in memory and on disk, for you.
Using URLImage
is dead simple:
URLImage(url: url) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
}
Take a look at some examples in the demo app.
URLImage
can be installed using Swift Package Manager.
In Xcode open File/Swift Packages/Add Package Dependency... menu.
Copy and paste the package URL:
https://github.com/dmytro-anokhin/url-image
For more details refer to Adding Package Dependencies to Your App documentation.
You can create URLImage
with URL and a ViewBuilder
to display downloaded image.
import URLImage // Import the package module
let url: URL = //...
URLImage(url) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
}
Note: first argument of the URLImage
initialiser is of URL
type, if you have a String
you must first create a URL
object.
URLImage
view manages and transitions between 4 download states:
Each of this states has a separate view. You can customize one or more using ViewBuilder
arguments.
URLImage(item.imageURL) {
// This view is displayed before download starts
EmptyView()
} inProgress: { progress in
// Display progress
Text("Loading...")
} failure: { error, retry in
// Display error and retry button
VStack {
Text(error.localizedDescription)
Button("Retry", action: retry)
}
} content: { image in
// Downloaded image
image
.resizable()
.aspectRatio(contentMode: .fit)
}
URLImage
allows to control certain aspects using URLImageOptions
structure. Things like whenever to download image or use cached, when to start and cancel download, how to configure network request, what is the maximum pixel size, etc.
URLImageOptions
is the environment value and can be set using \.urlImageOptions
key path.
URLImage(url) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
}
.environment(\.urlImageOptions, URLImageOptions(
maxPixelSize: CGSize(width: 600.0, height: 600.0)
))
Setting URLImageOptions
in the environment value allows to set options for a whole or a part of your views hierarchy.
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.urlImageOptions, URLImageOptions(
maxPixelSize: CGSize(width: 600.0, height: 600.0)
))
}
}
}
You can use ImageInfo
structure if you need information about an image, like actual size, or access the underlying CGImage
object. ImageInfo
is an argument of content
view builder closure.
URLImage(item.imageURL) { image, info in
if info.size.width < 1024.0 {
image
.resizable()
.aspectRatio(contentMode: .fit)
} else {
image
.resizable()
.aspectRatio(contentMode: .fill)
}
}
If you want to add ability to scale the image consider checking AdvancedScrollView package.
import AdvancedScrollView
import URLImage
URLImage(url) { image in
AdvancedScrollView(magnificationRange: 1.0...4.0) { _ in
image
}
}
URLImage
can also cache images to lower network bandwith or for offline use.
By default, URLImage
uses protocol cache policy, i.e. Cache-Control HTTP header and URLCache
. This corresponds to how images work on web and requires network connection.
Alternatively, if you want to view images offline, you must configure the file store. When configured, URLImage
will no longer use protocol cache policy, and instead follow URLImageOptions.FetchPolicy
setting.
import URLImage
import URLImageStore
@main
struct MyApp: App {
var body: some Scene {
let urlImageService = URLImageService(fileStore: URLImageFileStore(),
inMemoryStore: URLImageInMemoryStore())
return WindowGroup {
FeedListView()
.environment(\.urlImageService, urlImageService)
}
}
}
Make sure to include URLImageStore
library under "Frameworks, Libraries,and Embedded Content" of your target settings.
You may ask when to use protocol or custom cache. URLImage
designed to serve two use cases:
Use protocol cache policy when an app can only work connected to the internet. Ecommerce apps, such as shopping, travel, event reservation apps, etc., work like this. Following protocol cache policy you can be sure that images are cached in a way that your CDN defines, can still be accessed quickly, and don't take unnecessary space on user devices.
Configure URLImageStore
for content that needs to be accessed offline or downloaded in background. This can be a reader app, you probably want to download articles before user opens them, maybe while the app is in the background. This content should stay for a considerably long period of time.
URLImage
starts loading when the image view is rendered. In some cases (like with List
) you may want to start loading when view appears and cancel when it disappears. You can customize this using URLImageOptions.LoadOptions
options. You can combine multiple to achieve behaviour that fits your UI best.
List(/* ... */) {
// ...
}
.environment(\.urlImageOptions, URLImageOptions(loadOptions: [ .loadOnAppear, .cancelOnDisappear ]))
Note: versions prior to 3.1 start loading on appearance and cancel when view disappears. Version 3.1 starts loading when the view renders. This is because onAppear
and onDisappear
callbacks are quite unpredictable without context.
Alternatively you can make your own URLImage
to customize appearance and behaviour for your needs.
struct MyURLImage: View {
@ObservedObject private var remoteImage: RemoteImage
init(service: URLImageService, url: URL) {
remoteImage = service.makeRemoteImage(url: url, identifier: nil, options: URLImageOptions())
}
var body: some View {
ZStack {
switch remoteImage.loadingState {
case .success(let value):
value.image
default:
EmptyView()
}
}
.onAppear {
remoteImage.load()
}
}
}
You can access service environment value from enclosing view: @Environment(\.urlImageService) var service: URLImageService
.
You may want to download an image without a view. This is possible using the RemoteImagePublisher
object. The RemoteImagePublisher
can cache images for future use by the URLImage
view.
Download an image as CGImage
and ignore any errors:
cancellable = URLImageService.shared.remoteImagePublisher(url)
.tryMap { $0.cgImage }
.catch { _ in
Just(nil)
}
.sink { image in
// image is CGImage or nil
}
Download multiple images as an array of [CGImage?]
:
let publishers = urls.map { URLImageService.shared.remoteImagePublisher($0) }
cancellable = Publishers.MergeMany(publishers)
.tryMap { $0.cgImage }
.catch { _ in
Just(nil)
}
.collect()
.sink { images in
// images is [CGImage?]
}
When downloading image using the RemoteImagePublisher
object all options apply as they do for the URLImage
object. Be default downloaded image will be cached on the disk. This can speedup displaying images on later stage of your app. Also, this is currently the only supported way to display images in iOS 14 widgets.
Unfortunately views in WidgetKit can not run asynchronous operations: https://developer.apple.com/forums/thread/652581. The recommended way is to load your content, including images, in TimelineProvider
.
You can still use URLImage
for this. The idea is that you load image in TimelineProvider
using the RemoteImagePublisher
object, and display it in the URLImage
view.
URLImage
initialiser now omits an argument label for the first parameter, making URLImage(url: url)
just URLImage(url)
.URLImage
initialiser now uses ViewBuilder
attribute for closures that construct views.URLImageOptions
now passed in the environment, instead of as an argument. Custom identifier can still be passed as an argument of URLImage
.URLImage
uses protocol cache policy and URLCache
. This won't store images for offline usage. You can configure the file store as described in cache section.This is a common issue if you use URLImage
alongside TextField
or another control that updates a state that triggers view update. Because URLImage
is asynchronous and initially empty, it will reset to empty state before displaying downloaded image. To avoid this, setup URLImageInMemoryStore
somewhere in your App
.
import SwiftUI
import URLImage
import URLImageStore
@main
struct MyApp: App {
var body: some Scene {
let urlImageService = URLImageService(fileStore: nil, inMemoryStore: URLImageInMemoryStore())
return WindowGroup {
ContentView()
.environment(\.urlImageService, urlImageService)
}
}
}
Note: you can reset cached image using removeImageWithURL
, removeImageWithIdentifier
, or removeAllImages
methods of URLImageInMemoryStore
.
This is not a bug. Navigation/toolbar uses .renderingMode(.template)
to display images as templates (renders all non-transparent pixels as the foreground color). The way to reset it is to specify .renderingMode(.original)
:
URLImage(url) { image in
image.renderingMode(.original)
}
Use GitHub issues to report a bug. Include this information when possible:
Summary and/or background; OS and what device you are using; Version of URLImage library; What you expected would happen; What actually happens; Additional information: Screenshots or video demonstrating a bug; Crash log; Sample code, try isolating it so it compiles without dependancies; Test data: if you use public resource provide URLs of the images.
Please make sure there is a reproducible scenario. Ideally provide a sample code. And if you submit a sample code - make sure it compiles ;)
Use GitHub issues to request a feature.
Contributions are welcome. Please create a GitHub issue before submitting a pull request to plan and discuss implementation.
Author: Dmytro-anokhin
Source Code: https://github.com/dmytro-anokhin/url-image
License: MIT license
1653123600
This repository is a fork of SimpleMDE, made by Sparksuite. Go to the dedicated section for more information.
A drop-in JavaScript text area replacement for writing beautiful and understandable Markdown. EasyMDE allows users who may be less experienced with Markdown to use familiar toolbar buttons and shortcuts.
In addition, the syntax is rendered while editing to clearly show the expected result. Headings are larger, emphasized words are italicized, links are underlined, etc.
EasyMDE also features both built-in auto saving and spell checking. The editor is entirely customizable, from theming to toolbar buttons and javascript hooks.
Via npm:
npm install easymde
Via the UNPKG CDN:
<link rel="stylesheet" href="https://unpkg.com/easymde/dist/easymde.min.css">
<script src="https://unpkg.com/easymde/dist/easymde.min.js"></script>
Or jsDelivr:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css">
<script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script>
After installing and/or importing the module, you can load EasyMDE onto the first textarea
element on the web page:
<textarea></textarea>
<script>
const easyMDE = new EasyMDE();
</script>
Alternatively you can select a specific textarea
, via JavaScript:
<textarea id="my-text-area"></textarea>
<script>
const easyMDE = new EasyMDE({element: document.getElementById('my-text-area')});
</script>
Use easyMDE.value()
to get the content of the editor:
<script>
easyMDE.value();
</script>
Use easyMDE.value(val)
to set the content of the editor:
<script>
easyMDE.value('New input for **EasyMDE**');
</script>
true
, force downloads Font Awesome (used for icons). If set to false
, prevents downloading. Defaults to undefined
, which will intelligently check whether Font Awesome has already been included, then download accordingly.true
, focuses the editor automatically. Defaults to false
.true
, saves the text automatically. Defaults to false
.10000
(10 seconds).autosave.delay
or 10000
(10 seconds).locale: en-US, format: hour:minute
.{ delay: 300 }
, it will check every 300 ms if the editor is visible and if positive, call CodeMirror's refresh()
.**
or __
. Defaults to **
.```
or ~~~
. Defaults to ```
.*
or _
. Defaults to *
.*
, -
or +
. Defaults to *
.textarea
element to use. Defaults to the first textarea
element on the page.true
, force text changes made in EasyMDE to be immediately stored in original text area. Defaults to false
.false
, indent using spaces instead of tabs. Defaults to true
.false
by default, preview for images will appear only for images on separate lines.
as argument and returns a string that serves as the src
attribute of the <img>
tag in the preview. Enables dynamic previewing of images in the frontend without having to upload them to a server, allows copy-pasting of images to the editor with preview.["[", "](http://)"]
.true
, enables line numbers in the editor.false
, disable line wrapping. Defaults to true
."500px"
. Defaults to "300px"
.minHeight
option will be ignored. Should be a string containing a valid CSS value like "500px"
. Defaults to undefined
.true
when the editor is currently going into full screen mode, or false
.true
, will render headers without a space after the #
. Defaults to false
.false
, will not process GFM strikethrough syntax. Defaults to true
.true
, let underscores be a delimiter for separating words. Defaults to false
.false
, will replace CSS classes returned by the default Markdown mode. Otherwise the classes returned by the custom mode will be combined with the classes returned by the default mode. Defaults to true
."editor-preview"
.true
, a JS alert window appears asking for the link or image URL. Defaults to false
.URL of the image:
.URL for the link:
.true
, enables the image upload functionality, which can be triggered by drag and drop, copy-paste and through the browse-file window (opened when the user click on the upload-image icon). Defaults to false
.1024 * 1024 * 2
(2 MB).image/png, image/jpeg
.imageMaxSize
, imageAccept
, imageUploadEndpoint
and imageCSRFToken
ineffective.onSuccess
and onError
callback functions as parameters. onSuccess(imageUrl: string)
and onError(errorMessage: string)
{"data": {"filePath": "<filePath>"}}
where filePath is the path of the image (absolute if imagePathAbsolute
is set to true, relative if otherwise);{"error": "<errorCode>"}
, where errorCode can be noFileGiven
(HTTP 400 Bad Request), typeNotAllowed
(HTTP 415 Unsupported Media Type), fileTooLarge
(HTTP 413 Payload Too Large) or importError
(see errorMessages below). If errorCode is not one of the errorMessages, it is alerted unchanged to the user. This allows for server-side error messages. No default value.true
, will treat imageUrl
from imageUploadFunction
and filePath returned from imageUploadEndpoint
as an absolute rather than relative path, i.e. not prepend window.location.origin
to it.imageCSRFToken
has value, defaults to csrfmiddlewaretoken
.true
, passing CSRF token via header. Defaults to false
, which pass CSRF through request body.#image_name#
, #image_size#
and #image_max_size#
will replaced by their respective values, that can be used for customization or internationalization:uploadImage
is set to true
. Defaults to Attach files by drag and dropping or pasting from clipboard.
.Drop image to upload it.
.Uploading images #images_names#
.Uploading #file_name#: #progress#%
.Uploaded #image_name#
.B, KB, MB
(example: 218 KB
). You can use B,KB,MB
instead if you prefer without whitespaces (218KB
).errorCallback
option, where #image_name#
, #image_size#
and #image_max_size#
will replaced by their respective values, that can be used for customization or internationalization:You must select a file.
.imageAccept
list, or the server returned this error code. Defaults to This image type is not allowed.
.imageMaxSize
, or if the server returned this error code. Defaults to Image #image_name# is too big (#image_size#).\nMaximum file size is #image_max_size#.
.Something went wrong when uploading the image #image_name#.
.(errorMessage) => alert(errorMessage)
.true
, will highlight using highlight.js. Defaults to false
. To use this feature you must include highlight.js on your page or pass in using the hljs
option. For example, include the script and the CSS files like:<script src="https://cdn.jsdelivr.net/highlight.js/latest/highlight.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/highlight.js/latest/styles/github.min.css">
window.hljs
), you can provide an instance here. Defaults to undefined
.renderingConfig
options will take precedence.false
, disable parsing GitHub Flavored Markdown (GFM) single line breaks. Defaults to true
.false
, disable the spell checker. Defaults to true
. Optionally pass a CodeMirrorSpellChecker-compliant function.textarea
or contenteditable
. Defaults to textarea
for desktop and contenteditable
for mobile. contenteditable
option is necessary to enable nativeSpellcheck.false
, disable native spell checker. Defaults to true
.false
, allows side-by-side editing without going into fullscreen. Defaults to true
.false
, hide the status bar. Defaults to the array of built-in status bar items.false
, remove the CodeMirror-selectedtext
class from selected lines. Defaults to true
.false
, disable syncing scroll in side by side mode. Defaults to true
.2
.easymde
.false
, hide the toolbar. Defaults to the array of icons.false
, disable toolbar button tips. Defaults to true
.rtl
or ltr
. Changes text direction to support right-to-left languages. Defaults to ltr
.Most options demonstrate the non-default behavior:
const editor = new EasyMDE({
autofocus: true,
autosave: {
enabled: true,
uniqueId: "MyUniqueID",
delay: 1000,
submit_delay: 5000,
timeFormat: {
locale: 'en-US',
format: {
year: 'numeric',
month: 'long',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
},
},
text: "Autosaved: "
},
blockStyles: {
bold: "__",
italic: "_",
},
unorderedListStyle: "-",
element: document.getElementById("MyID"),
forceSync: true,
hideIcons: ["guide", "heading"],
indentWithTabs: false,
initialValue: "Hello world!",
insertTexts: {
horizontalRule: ["", "\n\n-----\n\n"],
image: [""],
link: ["[", "](https://)"],
table: ["", "\n\n| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Text | Text | Text |\n\n"],
},
lineWrapping: false,
minHeight: "500px",
parsingConfig: {
allowAtxHeaderWithoutSpace: true,
strikethrough: false,
underscoresBreakWords: true,
},
placeholder: "Type here...",
previewClass: "my-custom-styling",
previewClass: ["my-custom-styling", "more-custom-styling"],
previewRender: (plainText) => customMarkdownParser(plainText), // Returns HTML from a custom parser
previewRender: (plainText, preview) => { // Async method
setTimeout(() => {
preview.innerHTML = customMarkdownParser(plainText);
}, 250);
return "Loading...";
},
promptURLs: true,
promptTexts: {
image: "Custom prompt for URL:",
link: "Custom prompt for URL:",
},
renderingConfig: {
singleLineBreaks: false,
codeSyntaxHighlighting: true,
sanitizerFunction: (renderedHTML) => {
// Using DOMPurify and only allowing <b> tags
return DOMPurify.sanitize(renderedHTML, {ALLOWED_TAGS: ['b']})
},
},
shortcuts: {
drawTable: "Cmd-Alt-T"
},
showIcons: ["code", "table"],
spellChecker: false,
status: false,
status: ["autosave", "lines", "words", "cursor"], // Optional usage
status: ["autosave", "lines", "words", "cursor", {
className: "keystrokes",
defaultValue: (el) => {
el.setAttribute('data-keystrokes', 0);
},
onUpdate: (el) => {
const keystrokes = Number(el.getAttribute('data-keystrokes')) + 1;
el.innerHTML = `${keystrokes} Keystrokes`;
el.setAttribute('data-keystrokes', keystrokes);
},
}], // Another optional usage, with a custom status bar item that counts keystrokes
styleSelectedText: false,
sideBySideFullscreen: false,
syncSideBySidePreviewScroll: false,
tabSize: 4,
toolbar: false,
toolbarTips: false,
});
Below are the built-in toolbar icons (only some of which are enabled by default), which can be reorganized however you like. "Name" is the name of the icon, referenced in the JavaScript. "Action" is either a function or a URL to open. "Class" is the class given to the icon. "Tooltip" is the small tooltip that appears via the title=""
attribute. Note that shortcut hints are added automatically and reflect the specified action if it has a key bind assigned to it (i.e. with the value of action
set to bold
and that of tooltip
set to Bold
, the final text the user will see would be "Bold (Ctrl-B)").
Additionally, you can add a separator between any icons by adding "|"
to the toolbar array.
Name | Action | Tooltip Class |
---|---|---|
bold | toggleBold | Bold fa fa-bold |
italic | toggleItalic | Italic fa fa-italic |
strikethrough | toggleStrikethrough | Strikethrough fa fa-strikethrough |
heading | toggleHeadingSmaller | Heading fa fa-header |
heading-smaller | toggleHeadingSmaller | Smaller Heading fa fa-header |
heading-bigger | toggleHeadingBigger | Bigger Heading fa fa-lg fa-header |
heading-1 | toggleHeading1 | Big Heading fa fa-header header-1 |
heading-2 | toggleHeading2 | Medium Heading fa fa-header header-2 |
heading-3 | toggleHeading3 | Small Heading fa fa-header header-3 |
code | toggleCodeBlock | Code fa fa-code |
quote | toggleBlockquote | Quote fa fa-quote-left |
unordered-list | toggleUnorderedList | Generic List fa fa-list-ul |
ordered-list | toggleOrderedList | Numbered List fa fa-list-ol |
clean-block | cleanBlock | Clean block fa fa-eraser |
link | drawLink | Create Link fa fa-link |
image | drawImage | Insert Image fa fa-picture-o |
table | drawTable | Insert Table fa fa-table |
horizontal-rule | drawHorizontalRule | Insert Horizontal Line fa fa-minus |
preview | togglePreview | Toggle Preview fa fa-eye no-disable |
side-by-side | toggleSideBySide | Toggle Side by Side fa fa-columns no-disable no-mobile |
fullscreen | toggleFullScreen | Toggle Fullscreen fa fa-arrows-alt no-disable no-mobile |
guide | This link | Markdown Guide fa fa-question-circle |
undo | undo | Undo fa fa-undo |
redo | redo | Redo fa fa-redo |
Customize the toolbar using the toolbar
option.
Only the order of existing buttons:
const easyMDE = new EasyMDE({
toolbar: ["bold", "italic", "heading", "|", "quote"]
});
All information and/or add your own icons
const easyMDE = new EasyMDE({
toolbar: [
{
name: "bold",
action: EasyMDE.toggleBold,
className: "fa fa-bold",
title: "Bold",
},
"italics", // shortcut to pre-made button
{
name: "custom",
action: (editor) => {
// Add your own code
},
className: "fa fa-star",
title: "Custom Button",
attributes: { // for custom attributes
id: "custom-id",
"data-value": "custom value" // HTML5 data-* attributes need to be enclosed in quotation marks ("") because of the dash (-) in its name.
}
},
"|" // Separator
// [, ...]
]
});
Put some buttons on dropdown menu
const easyMDE = new EasyMDE({
toolbar: [{
name: "heading",
action: EasyMDE.toggleHeadingSmaller,
className: "fa fa-header",
title: "Headers",
},
"|",
{
name: "others",
className: "fa fa-blind",
title: "others buttons",
children: [
{
name: "image",
action: EasyMDE.drawImage,
className: "fa fa-picture-o",
title: "Image",
},
{
name: "quote",
action: EasyMDE.toggleBlockquote,
className: "fa fa-percent",
title: "Quote",
},
{
name: "link",
action: EasyMDE.drawLink,
className: "fa fa-link",
title: "Link",
}
]
},
// [, ...]
]
});
EasyMDE comes with an array of predefined keyboard shortcuts, but they can be altered with a configuration option. The list of default ones is as follows:
Shortcut (Windows / Linux) | Shortcut (macOS) | Action |
---|---|---|
Ctrl-' | Cmd-' | "toggleBlockquote" |
Ctrl-B | Cmd-B | "toggleBold" |
Ctrl-E | Cmd-E | "cleanBlock" |
Ctrl-H | Cmd-H | "toggleHeadingSmaller" |
Ctrl-I | Cmd-I | "toggleItalic" |
Ctrl-K | Cmd-K | "drawLink" |
Ctrl-L | Cmd-L | "toggleUnorderedList" |
Ctrl-P | Cmd-P | "togglePreview" |
Ctrl-Alt-C | Cmd-Alt-C | "toggleCodeBlock" |
Ctrl-Alt-I | Cmd-Alt-I | "drawImage" |
Ctrl-Alt-L | Cmd-Alt-L | "toggleOrderedList" |
Shift-Ctrl-H | Shift-Cmd-H | "toggleHeadingBigger" |
F9 | F9 | "toggleSideBySide" |
F11 | F11 | "toggleFullScreen" |
Here is how you can change a few, while leaving others untouched:
const editor = new EasyMDE({
shortcuts: {
"toggleOrderedList": "Ctrl-Alt-K", // alter the shortcut for toggleOrderedList
"toggleCodeBlock": null, // unbind Ctrl-Alt-C
"drawTable": "Cmd-Alt-T", // bind Cmd-Alt-T to drawTable action, which doesn't come with a default shortcut
}
});
Shortcuts are automatically converted between platforms. If you define a shortcut as "Cmd-B", on PC that shortcut will be changed to "Ctrl-B". Conversely, a shortcut defined as "Ctrl-B" will become "Cmd-B" for Mac users.
The list of actions that can be bound is the same as the list of built-in actions available for toolbar buttons.
You can catch the following list of events: https://codemirror.net/doc/manual.html#events
const easyMDE = new EasyMDE();
easyMDE.codemirror.on("change", () => {
console.log(easyMDE.value());
});
You can revert to the initial text area by calling the toTextArea
method. Note that this clears up the autosave (if enabled) associated with it. The text area will retain any text from the destroyed EasyMDE instance.
const easyMDE = new EasyMDE();
// ...
easyMDE.toTextArea();
easyMDE = null;
If you need to remove registered event listeners (when the editor is not needed anymore), call easyMDE.cleanup()
.
The following self-explanatory methods may be of use while developing with EasyMDE.
const easyMDE = new EasyMDE();
easyMDE.isPreviewActive(); // returns boolean
easyMDE.isSideBySideActive(); // returns boolean
easyMDE.isFullscreenActive(); // returns boolean
easyMDE.clearAutosavedValue(); // no returned value
EasyMDE is a continuation of SimpleMDE.
SimpleMDE began as an improvement of lepture's Editor project, but has now taken on an identity of its own. It is bundled with CodeMirror and depends on Font Awesome.
CodeMirror is the backbone of the project and parses much of the Markdown syntax as it's being written. This allows us to add styles to the Markdown that's being written. Additionally, a toolbar and status bar have been added to the top and bottom, respectively. Previews are rendered by Marked using GitHub Flavored Markdown (GFM).
I originally made this fork to implement FontAwesome 5 compatibility into SimpleMDE. When that was done I submitted a pull request, which has not been accepted yet. This, and the project being inactive since May 2017, triggered me to make more changes and try to put new life into the project.
Changes include:
https://
by defaultMy intention is to continue development on this project, improving it and keeping it alive.
You may want to edit this library to adapt its behavior to your needs. This can be done in some quick steps:
gulp
command, which will generate files: dist/easymde.min.css
and dist/easymde.min.js
;Want to contribute to EasyMDE? Thank you! We have a contribution guide just for you!
Author: Ionaru
Source Code: https://github.com/Ionaru/easy-markdown-editor
License: MIT license
1672884972
URLImage
is a SwiftUI view that displays an image downloaded from provided URL. URLImage
manages downloading remote image and caching it locally, both in memory and on disk, for you.
Using URLImage
is dead simple:
URLImage(url: url) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
}
Take a look at some examples in the demo app.
URLImage
can be installed using Swift Package Manager.
In Xcode open File/Swift Packages/Add Package Dependency... menu.
Copy and paste the package URL:
https://github.com/dmytro-anokhin/url-image
For more details refer to Adding Package Dependencies to Your App documentation.
You can create URLImage
with URL and a ViewBuilder
to display downloaded image.
import URLImage // Import the package module
let url: URL = //...
URLImage(url) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
}
Note: first argument of the URLImage
initialiser is of URL
type, if you have a String
you must first create a URL
object.
URLImage
view manages and transitions between 4 download states:
Each of this states has a separate view. You can customize one or more using ViewBuilder
arguments.
URLImage(item.imageURL) {
// This view is displayed before download starts
EmptyView()
} inProgress: { progress in
// Display progress
Text("Loading...")
} failure: { error, retry in
// Display error and retry button
VStack {
Text(error.localizedDescription)
Button("Retry", action: retry)
}
} content: { image in
// Downloaded image
image
.resizable()
.aspectRatio(contentMode: .fit)
}
URLImage
allows to control certain aspects using URLImageOptions
structure. Things like whenever to download image or use cached, when to start and cancel download, how to configure network request, what is the maximum pixel size, etc.
URLImageOptions
is the environment value and can be set using \.urlImageOptions
key path.
URLImage(url) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
}
.environment(\.urlImageOptions, URLImageOptions(
maxPixelSize: CGSize(width: 600.0, height: 600.0)
))
Setting URLImageOptions
in the environment value allows to set options for a whole or a part of your views hierarchy.
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.urlImageOptions, URLImageOptions(
maxPixelSize: CGSize(width: 600.0, height: 600.0)
))
}
}
}
You can use ImageInfo
structure if you need information about an image, like actual size, or access the underlying CGImage
object. ImageInfo
is an argument of content
view builder closure.
URLImage(item.imageURL) { image, info in
if info.size.width < 1024.0 {
image
.resizable()
.aspectRatio(contentMode: .fit)
} else {
image
.resizable()
.aspectRatio(contentMode: .fill)
}
}
If you want to add ability to scale the image consider checking AdvancedScrollView package.
import AdvancedScrollView
import URLImage
URLImage(url) { image in
AdvancedScrollView(magnificationRange: 1.0...4.0) { _ in
image
}
}
URLImage
can also cache images to lower network bandwith or for offline use.
By default, URLImage
uses protocol cache policy, i.e. Cache-Control HTTP header and URLCache
. This corresponds to how images work on web and requires network connection.
Alternatively, if you want to view images offline, you must configure the file store. When configured, URLImage
will no longer use protocol cache policy, and instead follow URLImageOptions.FetchPolicy
setting.
import URLImage
import URLImageStore
@main
struct MyApp: App {
var body: some Scene {
let urlImageService = URLImageService(fileStore: URLImageFileStore(),
inMemoryStore: URLImageInMemoryStore())
return WindowGroup {
FeedListView()
.environment(\.urlImageService, urlImageService)
}
}
}
Make sure to include URLImageStore
library under "Frameworks, Libraries,and Embedded Content" of your target settings.
You may ask when to use protocol or custom cache. URLImage
designed to serve two use cases:
Use protocol cache policy when an app can only work connected to the internet. Ecommerce apps, such as shopping, travel, event reservation apps, etc., work like this. Following protocol cache policy you can be sure that images are cached in a way that your CDN defines, can still be accessed quickly, and don't take unnecessary space on user devices.
Configure URLImageStore
for content that needs to be accessed offline or downloaded in background. This can be a reader app, you probably want to download articles before user opens them, maybe while the app is in the background. This content should stay for a considerably long period of time.
URLImage
starts loading when the image view is rendered. In some cases (like with List
) you may want to start loading when view appears and cancel when it disappears. You can customize this using URLImageOptions.LoadOptions
options. You can combine multiple to achieve behaviour that fits your UI best.
List(/* ... */) {
// ...
}
.environment(\.urlImageOptions, URLImageOptions(loadOptions: [ .loadOnAppear, .cancelOnDisappear ]))
Note: versions prior to 3.1 start loading on appearance and cancel when view disappears. Version 3.1 starts loading when the view renders. This is because onAppear
and onDisappear
callbacks are quite unpredictable without context.
Alternatively you can make your own URLImage
to customize appearance and behaviour for your needs.
struct MyURLImage: View {
@ObservedObject private var remoteImage: RemoteImage
init(service: URLImageService, url: URL) {
remoteImage = service.makeRemoteImage(url: url, identifier: nil, options: URLImageOptions())
}
var body: some View {
ZStack {
switch remoteImage.loadingState {
case .success(let value):
value.image
default:
EmptyView()
}
}
.onAppear {
remoteImage.load()
}
}
}
You can access service environment value from enclosing view: @Environment(\.urlImageService) var service: URLImageService
.
You may want to download an image without a view. This is possible using the RemoteImagePublisher
object. The RemoteImagePublisher
can cache images for future use by the URLImage
view.
Download an image as CGImage
and ignore any errors:
cancellable = URLImageService.shared.remoteImagePublisher(url)
.tryMap { $0.cgImage }
.catch { _ in
Just(nil)
}
.sink { image in
// image is CGImage or nil
}
Download multiple images as an array of [CGImage?]
:
let publishers = urls.map { URLImageService.shared.remoteImagePublisher($0) }
cancellable = Publishers.MergeMany(publishers)
.tryMap { $0.cgImage }
.catch { _ in
Just(nil)
}
.collect()
.sink { images in
// images is [CGImage?]
}
When downloading image using the RemoteImagePublisher
object all options apply as they do for the URLImage
object. Be default downloaded image will be cached on the disk. This can speedup displaying images on later stage of your app. Also, this is currently the only supported way to display images in iOS 14 widgets.
Unfortunately views in WidgetKit can not run asynchronous operations: https://developer.apple.com/forums/thread/652581. The recommended way is to load your content, including images, in TimelineProvider
.
You can still use URLImage
for this. The idea is that you load image in TimelineProvider
using the RemoteImagePublisher
object, and display it in the URLImage
view.
URLImage
initialiser now omits an argument label for the first parameter, making URLImage(url: url)
just URLImage(url)
.URLImage
initialiser now uses ViewBuilder
attribute for closures that construct views.URLImageOptions
now passed in the environment, instead of as an argument. Custom identifier can still be passed as an argument of URLImage
.URLImage
uses protocol cache policy and URLCache
. This won't store images for offline usage. You can configure the file store as described in cache section.This is a common issue if you use URLImage
alongside TextField
or another control that updates a state that triggers view update. Because URLImage
is asynchronous and initially empty, it will reset to empty state before displaying downloaded image. To avoid this, setup URLImageInMemoryStore
somewhere in your App
.
import SwiftUI
import URLImage
import URLImageStore
@main
struct MyApp: App {
var body: some Scene {
let urlImageService = URLImageService(fileStore: nil, inMemoryStore: URLImageInMemoryStore())
return WindowGroup {
ContentView()
.environment(\.urlImageService, urlImageService)
}
}
}
Note: you can reset cached image using removeImageWithURL
, removeImageWithIdentifier
, or removeAllImages
methods of URLImageInMemoryStore
.
This is not a bug. Navigation/toolbar uses .renderingMode(.template)
to display images as templates (renders all non-transparent pixels as the foreground color). The way to reset it is to specify .renderingMode(.original)
:
URLImage(url) { image in
image.renderingMode(.original)
}
Use GitHub issues to report a bug. Include this information when possible:
Summary and/or background; OS and what device you are using; Version of URLImage library; What you expected would happen; What actually happens; Additional information: Screenshots or video demonstrating a bug; Crash log; Sample code, try isolating it so it compiles without dependancies; Test data: if you use public resource provide URLs of the images.
Please make sure there is a reproducible scenario. Ideally provide a sample code. And if you submit a sample code - make sure it compiles ;)
Use GitHub issues to request a feature.
Contributions are welcome. Please create a GitHub issue before submitting a pull request to plan and discuss implementation.
Author: Dmytro-anokhin
Source Code: https://github.com/dmytro-anokhin/url-image
License: MIT license
1641693600
Si es un desarrollador de Python que está pensando en comenzar con el desarrollo móvil, entonces el marco Kivy es su mejor opción. Con Kivy, puede desarrollar aplicaciones independientes de la plataforma que compilan para iOS, Android, Windows, macOS y Linux. En este artículo, cubriremos Android específicamente porque es el más utilizado.
Construiremos una aplicación generadora de números aleatorios simple que puede instalar en su teléfono y probar cuando haya terminado. Para continuar con este artículo, debe estar familiarizado con Python. ¡Empecemos!
Primero, necesitará un nuevo directorio para su aplicación. Asegúrese de tener Python instalado en su máquina y abra un nuevo archivo de Python. Deberá instalar el módulo Kivy desde su terminal usando cualquiera de los comandos a continuación. Para evitar conflictos de paquetes, asegúrese de instalar Kivy en un entorno virtual:
pip install kivy
//
pip3 install kivy
Una vez que haya instalado Kivy, debería ver un mensaje de éxito de su terminal que se parece a las capturas de pantalla a continuación:
Instalación decepcionada
Instalación exitosa de Kivy
A continuación, navegue a la carpeta de su proyecto. En el main.py
archivo, necesitaremos importar el módulo Kivy y especificar qué versión queremos. Puede usar Kivy v2.0.0, pero si tiene un teléfono inteligente anterior a Android 8.0, le recomiendo usar Kivy v1.9.0. Puede jugar con las diferentes versiones durante la compilación para ver las diferencias en las características y el rendimiento.
Agregue el número de versión justo después de la import kivy
línea de la siguiente manera:
kivy.require('1.9.0')
Ahora, crearemos una clase que básicamente definirá nuestra aplicación; Voy a nombrar el mío RandomNumber
. Esta clase heredará la app
clase de Kivy. Por lo tanto, debe importar app
agregando from kivy.app import App
:
class RandomNumber(App):
En la RandomNumber
clase, deberá agregar una función llamada build
, que toma un self
parámetro. Para devolver la interfaz de usuario, usaremos la build
función. Por ahora, lo tengo devuelto como una simple etiqueta. Para hacerlo, deberá importar Label
usando la línea from kivy.uix.label import Label
:
import kivy
from kivy.app import App
from kivy.uix.label import Label
class RandomNumber(App):
def build(self):
return Label(text="Random Number Generator")
¡Ahora, el esqueleto de nuestra aplicación está completo! Antes de continuar, debe crear una instancia de la RandomNumber
clase y ejecutarla en su terminal o IDE para ver la interfaz:
importar kivy de kivy.app importar aplicación de kivy.uix.label clase de etiqueta de importación RandomNumber(App): def build(self): return Label(text="Generador de números aleatorios") randomApp = RandomNumber() randomApp.run()
Cuando ejecuta la instancia de clase con el texto Random Number Generator
, debería ver una interfaz o ventana simple que se parece a la siguiente captura de pantalla:
Interfaz simple después de ejecutar el código.
No podrá ejecutar el texto en Android hasta que haya terminado de construir todo.
A continuación, necesitaremos una forma de subcontratar la interfaz. Primero, crearemos un archivo Kivy en nuestro directorio que albergará la mayor parte de nuestro trabajo de diseño. Querrá nombrar este archivo con el mismo nombre que su clase usando letras minúsculas y una .kv
extensión. Kivy asociará automáticamente el nombre de la clase y el nombre del archivo, pero es posible que no funcione en Android si son exactamente iguales.
Dentro de ese .kv
archivo, debe especificar el diseño de su aplicación, incluidos elementos como la etiqueta, los botones, los formularios, etc. Para simplificar esta demostración, agregaré una etiqueta para el título Random Number
, una etiqueta que servirá como marcador de posición. para el número aleatorio que se genera _
, y un Generate
botón que llama a la generate
función.
Mi .kv
archivo se parece al siguiente código, pero puede jugar con los diferentes valores para que se ajusten a sus requisitos:
<boxLayout>:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
En el main.py
archivo, ya no necesita la Label
declaración de importación porque el archivo Kivy se encarga de su interfaz de usuario. Sin embargo, necesita importar boxlayout
, que utilizará en el archivo Kivy.
En su archivo principal, debe agregar la declaración de importación y editar su main.py
archivo para leer return BoxLayout()
el build
método:
from kivy.uix.boxlayout import BoxLayout
Si ejecuta el comando anterior, debería ver una interfaz simple que tiene el título del número aleatorio, el _
marcador de posición y el generate
botón en el que se puede hacer clic:
Aplicación de números aleatorios renderizada
Tenga en cuenta que no tuvo que importar nada para que funcione el archivo Kivy. Básicamente, cuando ejecuta la aplicación, regresa boxlayout
buscando un archivo dentro del archivo Kivy con el mismo nombre que su clase. Tenga en cuenta que esta es una interfaz simple y puede hacer que su aplicación sea tan robusta como desee. Asegúrese de consultar la documentación del idioma Kv .
Ahora que nuestra aplicación está casi terminada, necesitaremos una función simple para generar números aleatorios cuando un usuario haga clic en el generate
botón y luego mostrar ese número aleatorio en la interfaz de la aplicación. Para hacerlo, necesitaremos cambiar algunas cosas en nuestros archivos.
Primero, importaremos el módulo que usaremos para generar un número aleatorio con import random
. Luego, crearemos una función o método que llame al número generado. Para esta demostración, usaré un rango entre 0
y 2000
. Generar el número aleatorio es simple con el random.randint(0, 2000)
comando. Agregaremos esto a nuestro código en un momento.
A continuación, crearemos otra clase que será nuestra propia versión del box layout
. Nuestra clase tendrá que heredar la box layout
clase, que alberga el método para generar números aleatorios y representarlos en la interfaz:
class MyRoot(BoxLayout):
def __init__(self):
super(MyRoot, self).__init__()
Dentro de esa clase, crearemos el generate
método, que no solo generará números aleatorios, sino que también manipulará la etiqueta que controla lo que se muestra como número aleatorio en el archivo Kivy.
Para acomodar este método, primero necesitaremos hacer cambios en el .kv
archivo. Dado que la MyRoot
clase ha heredado el box layout
, puede crear MyRoot
el elemento de nivel superior en su .kv
archivo:
<MyRoot>:
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
Tenga en cuenta que todavía mantiene todas las especificaciones de la interfaz de usuario con sangría en el archivo Box Layout
. Después de esto, debe agregar una identificación a la etiqueta que contendrá los números generados, lo que facilita la manipulación cuando generate
se llama a la función. Debe especificar la relación entre la ID en este archivo y otra en el código principal en la parte superior, justo antes de la BoxLayout
línea:
<MyRoot>:
random_label: random_label
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
id: random_label
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
La random_label: random_label
línea básicamente significa que la etiqueta con el ID random_label
se asignará a random_label
en el main.py
archivo, lo que significa que cualquier acción que manipula random_label
serán mapeados en la etiqueta con el nombre especificado.
Ahora podemos crear el método para generar el número aleatorio en el archivo principal:
def generate_number(self):
self.random_label.text = str(random.randint(0, 2000))
# notice how the class method manipulates the text attributre of the random label by a# ssigning it a new random number generate by the 'random.randint(0, 2000)' funcion. S# ince this the random number generated is an integer, typecasting is required to make # it a string otherwise you will get a typeError in your terminal when you run it.
La MyRoot
clase debería parecerse al siguiente código:
class MyRoot(BoxLayout):
def __init__(self):
super(MyRoot, self).__init__()
def generate_number(self):
self.random_label.text = str(random.randint(0, 2000))
¡Felicidades! Ya ha terminado con el archivo principal de la aplicación. Lo único que queda por hacer es asegurarse de llamar a esta función cuando se haga generate
clic en el botón. Solo necesita agregar la línea on_press: root.generate_number()
a la parte de selección de botones de su .kv
archivo:
<MyRoot>:
random_label: random_label
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
id: random_label
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
on_press: root.generate_number()
Ahora, puede ejecutar la aplicación.
Antes de compilar nuestra aplicación en Android, tengo malas noticias para los usuarios de Windows. Necesitará Linux o macOS para compilar su aplicación de Android. Sin embargo, no necesita tener una distribución de Linux separada, en su lugar, puede usar una máquina virtual.
Para compilar y generar una .apk
aplicación Android completa , usaremos una herramienta llamada Buildozer . Instalemos Buildozer a través de nuestra terminal usando uno de los siguientes comandos:
pip3 install buildozer
//
pip install buildozer
Ahora, instalaremos algunas de las dependencias requeridas de Buildozer. Estoy en Linux Ergo, así que usaré comandos específicos de Linux. Debe ejecutar estos comandos uno por uno:
sudo apt update
sudo apt install -y git zip unzip openjdk-13-jdk python3-pip autoconf libtool pkg-config zlib1g-dev libncurses5-dev libncursesw5-dev libtinfo5 cmake libffi-dev libssl-dev
pip3 install --upgrade Cython==0.29.19 virtualenv
# add the following line at the end of your ~/.bashrc file
export PATH=$PATH:~/.local/bin/
Después de ejecutar los comandos específicos, ejecute buildozer init
. Debería ver un resultado similar a la captura de pantalla a continuación:
Inicialización exitosa de Buildozer
El comando anterior crea un .spec
archivo Buildozer , que puede usar para hacer especificaciones para su aplicación, incluido el nombre de la aplicación, el ícono, etc. El .spec
archivo debe verse como el bloque de código a continuación:
[app]
# (str) Title of your application
title = My Application
# (str) Package name
package.name = myapp
# (str) Package domain (needed for android/ios packaging)
package.domain = org.test
# (str) Source code where the main.py live
source.dir = .
# (list) Source files to include (let empty to include all the files)
source.include_exts = py,png,jpg,kv,atlas
# (list) List of inclusions using pattern matching
#source.include_patterns = assets/*,images/*.png
# (list) Source files to exclude (let empty to not exclude anything)
#source.exclude_exts = spec
# (list) List of directory to exclude (let empty to not exclude anything)
#source.exclude_dirs = tests, bin
# (list) List of exclusions using pattern matching
#source.exclude_patterns = license,images/*/*.jpg
# (str) Application versioning (method 1)
version = 0.1
# (str) Application versioning (method 2)
# version.regex = __version__ = \['"\](.*)['"]
# version.filename = %(source.dir)s/main.py
# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy
# (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes
# requirements.source.kivy = ../../kivy
# (list) Garden requirements
#garden_requirements =
# (str) Presplash of the application
#presplash.filename = %(source.dir)s/data/presplash.png
# (str) Icon of the application
#icon.filename = %(source.dir)s/data/icon.png
# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
orientation = portrait
# (list) List of service to declare
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY
#
# OSX Specific
#
#
# author = © Copyright Info
# change the major version of python used by the app
osx.python_version = 3
# Kivy version to use
osx.kivy_version = 1.9.1
#
# Android specific
#
# (bool) Indicate if the application should be fullscreen or not
fullscreen = 0
# (string) Presplash background color (for new android toolchain)
# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
# olive, purple, silver, teal.
#android.presplash_color = #FFFFFF
# (list) Permissions
#android.permissions = INTERNET
# (int) Target Android API, should be as high as possible.
#android.api = 27
# (int) Minimum API your APK will support.
#android.minapi = 21
# (int) Android SDK version to use
#android.sdk = 20
# (str) Android NDK version to use
#android.ndk = 19b
# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
#android.ndk_api = 21
# (bool) Use --private data storage (True) or --dir public storage (False)
#android.private_storage = True
# (str) Android NDK directory (if empty, it will be automatically downloaded.)
#android.ndk_path =
# (str) Android SDK directory (if empty, it will be automatically downloaded.)
#android.sdk_path =
# (str) ANT directory (if empty, it will be automatically downloaded.)
#android.ant_path =
# (bool) If True, then skip trying to update the Android sdk
# This can be useful to avoid excess Internet downloads or save time
# when an update is due and you just want to test/build your package
# android.skip_update = False
# (bool) If True, then automatically accept SDK license
# agreements. This is intended for automation only. If set to False,
# the default, you will be shown the license when first running
# buildozer.
# android.accept_sdk_license = False
# (str) Android entry point, default is ok for Kivy-based app
#android.entrypoint = org.renpy.android.PythonActivity
# (str) Android app theme, default is ok for Kivy-based app
# android.apptheme = "@android:style/Theme.NoTitleBar"
# (list) Pattern to whitelist for the whole project
#android.whitelist =
# (str) Path to a custom whitelist file
#android.whitelist_src =
# (str) Path to a custom blacklist file
#android.blacklist_src =
# (list) List of Java .jar files to add to the libs so that pyjnius can access
# their classes. Don't add jars that you do not need, since extra jars can slow
# down the build process. Allows wildcards matching, for example:
# OUYA-ODK/libs/*.jar
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
#android.add_src =
# (list) Android AAR archives to add (currently works only with sdl2_gradle
# bootstrap)
#android.add_aars =
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
#android.gradle_dependencies =
# (list) add java compile options
# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option
# see https://developer.android.com/studio/write/java8-support for further information
# android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"
# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
# please enclose in double quotes
# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }"
#android.add_gradle_repositories =
# (list) packaging options to add
# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
# can be necessary to solve conflicts in gradle_dependencies
# please enclose in double quotes
# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'"
#android.add_gradle_repositories =
# (list) Java classes to add as activities to the manifest.
#android.add_activities = com.example.ExampleActivity
# (str) OUYA Console category. Should be one of GAME or APP
# If you leave this blank, OUYA support will not be enabled
#android.ouya.category = GAME
# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =
# (str) launchMode to set for the main activity
#android.manifest.launch_mode = standard
# (list) Android additional libraries to copy into libs/armeabi
#android.add_libs_armeabi = libs/android/*.so
#android.add_libs_armeabi_v7a = libs/android-v7/*.so
#android.add_libs_arm64_v8a = libs/android-v8/*.so
#android.add_libs_x86 = libs/android-x86/*.so
#android.add_libs_mips = libs/android-mips/*.so
# (bool) Indicate whether the screen should stay on
# Don't forget to add the WAKE_LOCK permission if you set this to True
#android.wakelock = False
# (list) Android application meta-data to set (key=value format)
#android.meta_data =
# (list) Android library project to add (will be added in the
# project.properties automatically.)
#android.library_references =
# (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
#android.uses_library =
# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D
# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1
# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
android.arch = armeabi-v7a
# (int) overrides automatic versionCode computation (used in build.gradle)
# this is not the same as app version and should only be edited if you know what you're doing
# android.numeric_version = 1
#
# Python for android (p4a) specific
#
# (str) python-for-android fork to use, defaults to upstream (kivy)
#p4a.fork = kivy
# (str) python-for-android branch to use, defaults to master
#p4a.branch = master
# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
#p4a.source_dir =
# (str) The directory in which python-for-android should look for your own build recipes (if any)
#p4a.local_recipes =
# (str) Filename to the hook for p4a
#p4a.hook =
# (str) Bootstrap to use for android builds
# p4a.bootstrap = sdl2
# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
#p4a.port =
#
# iOS specific
#
# (str) Path to a custom kivy-ios folder
#ios.kivy_ios_dir = ../kivy-ios
# Alternately, specify the URL and branch of a git checkout:
ios.kivy_ios_url = https://github.com/kivy/kivy-ios
ios.kivy_ios_branch = master
# Another platform dependency: ios-deploy
# Uncomment to use a custom checkout
#ios.ios_deploy_dir = ../ios_deploy
# Or specify URL and branch
ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
ios.ios_deploy_branch = 1.7.0
# (str) Name of the certificate to use for signing the debug version
# Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"
# (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s
[buildozer]
# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
log_level = 2
# (int) Display warning if buildozer is run as root (0 = False, 1 = True)
warn_on_root = 1
# (str) Path to build artifact storage, absolute or relative to spec file
# build_dir = ./.buildozer
# (str) Path to build output (i.e. .apk, .ipa) storage
# bin_dir = ./bin
# -----------------------------------------------------------------------------
# List as sections
#
# You can define all the "list" as [section:key].
# Each line will be considered as a option to the list.
# Let's take [app] / source.exclude_patterns.
# Instead of doing:
#
#[app]
#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
#
# This can be translated into:
#
#[app:source.exclude_patterns]
#license
#data/audio/*.wav
#data/images/original/*
#
# -----------------------------------------------------------------------------
# Profiles
#
# You can extend section / key with a profile
# For example, you want to deploy a demo version of your application without
# HD content. You could first change the title to add "(demo)" in the name
# and extend the excluded directories to remove the HD content.
#
#[app@demo]
#title = My Application (demo)
#
#[app:source.exclude_patterns@demo]
#images/hd/*
#
# Then, invoke the command line with the "demo" profile:
#
#buildozer --profile demo android debug
Si desea especificar cosas como el ícono, los requisitos, la pantalla de carga, etc., debe editar este archivo. Después de realizar todas las ediciones deseadas en su aplicación, ejecute buildozer -v android debug
desde el directorio de su aplicación para construir y compilar su aplicación. Esto puede llevar un tiempo, especialmente si tiene una máquina lenta.
Una vez finalizado el proceso, su terminal debería tener algunos registros, uno que confirme que la compilación fue exitosa:
Construcción exitosa de Android
También debe tener una versión APK de su aplicación en su directorio bin. Este es el ejecutable de la aplicación que instalará y ejecutará en su teléfono:
Android .apk en el directorio bin
¡Felicidades! Si ha seguido este tutorial paso a paso, debería tener una aplicación simple de generador de números aleatorios en su teléfono. Juega con él y ajusta algunos valores, luego reconstruye. Ejecutar la reconstrucción no llevará tanto tiempo como la primera compilación.
Como puede ver, crear una aplicación móvil con Python es bastante sencillo , siempre que esté familiarizado con el marco o módulo con el que está trabajando. Independientemente, la lógica se ejecuta de la misma manera.
Familiarícese con el módulo Kivy y sus widgets. Nunca se puede saber todo a la vez. Solo necesita encontrar un proyecto y mojarse los pies lo antes posible. Codificación feliz.
Enlace: https://blog.logrocket.com/build-android-application-kivy-python-framework/
1641693600
あなたがモバイル開発を始めることを考えているPython開発者なら、Kivyフレームワークが最善の策です。Kivyを使用すると、iOS、Android、Windows、macOS、およびLinux用にコンパイルされるプラットフォームに依存しないアプリケーションを開発できます。この記事では、Androidが最も使用されているため、特にAndroidについて説明します。
簡単な乱数ジェネレーターアプリを作成します。このアプリを携帯電話にインストールして、完了したらテストできます。この記事を続けるには、Pythonに精通している必要があります。始めましょう!
まず、アプリ用の新しいディレクトリが必要になります。マシンにPythonがインストールされていることを確認し、新しいPythonファイルを開きます。以下のコマンドのいずれかを使用して、ターミナルからKivyモジュールをインストールする必要があります。パッケージの競合を避けるために、Kivyを仮想環境にインストールしていることを確認してください。
pip install kivy
//
pip3 install kivy
Kivyをインストールすると、以下のスクリーンショットのような成功メッセージがターミナルから表示されます。
がっかりしたインストール
Kivyのインストールに成功
次に、プロジェクトフォルダに移動します。このmain.py
ファイルで、Kivyモジュールをインポートし、必要なバージョンを指定する必要があります。Kivy v2.0.0を使用できますが、Android 8.0より古いスマートフォンを使用している場合は、Kivyv1.9.0を使用することをお勧めします。ビルド中にさまざまなバージョンをいじって、機能とパフォーマンスの違いを確認できます。
import kivy
次のように、行の直後にバージョン番号を追加します。
kivy.require('1.9.0')
次に、基本的にアプリを定義するクラスを作成します。私の名前を付けますRandomNumber
。このクラスはapp
Kivyからクラスを継承します。したがって、次app
を追加してインポートする必要がありますfrom kivy.app import App
。
class RandomNumber(App):
ではRandomNumber
クラスは、呼び出された関数を追加する必要がありますbuild
とり、self
パラメータを。実際にUIを返すには、このbuild
関数を使用します。今のところ、単純なラベルとして返送しています。そのためには、次Label
の行を使用してインポートする必要がありますfrom kivy.uix.label import Label
。
import kivy
from kivy.app import App
from kivy.uix.label import Label
class RandomNumber(App):
def build(self):
return Label(text="Random Number Generator")
これで、アプリのスケルトンが完成しました。先に進む前に、RandomNumber
クラスのインスタンスを作成し、ターミナルまたはIDEで実行して、インターフェイスを確認する必要があります。
import kivy from kivy.app import App from kivy.uix.label import Label class RandomNumber(App):def build(self):return Label(text = "Random Number Generator")randomApp = RandomNumber()randomApp.run()
テキストを使用してクラスインスタンスを実行すると、Random Number Generator
次のスクリーンショットのような単純なインターフェイスまたはウィンドウが表示されます。
コードを実行した後のシンプルなインターフェイス
すべての構築が完了するまで、Androidでテキストを実行することはできません。
次に、インターフェースをアウトソーシングする方法が必要になります。まず、ディレクトリにKivyファイルを作成します。このファイルには、ほとんどの設計作業が含まれています。このファイルには、小文字と.kv
拡張子を使用して、クラスと同じ名前を付けることができます。Kivyはクラス名とファイル名を自動的に関連付けますが、それらがまったく同じである場合、Androidでは機能しない可能性があります。
その.kv
ファイル内で、ラベル、ボタン、フォームなどの要素を含むアプリのレイアウトを指定する必要があります。このデモを簡単にするために、タイトルRandom Number
のラベル、プレースホルダーとして機能するラベルを追加します。生成される乱数_
、および関数Generate
を呼び出すボタンgenerate
。
私の.kv
ファイルは以下のコードのように見えますが、要件に合わせてさまざまな値をいじることができます。
<boxLayout>:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
このmain.py
ファイルではLabel
、KivyファイルがUIを処理するため、importステートメントは不要になりました。ただし、boxlayout
Kivyファイルで使用するをインポートする必要があります。
メインファイルで、importステートメントを追加し、main.py
ファイルを編集return BoxLayout()
してbuild
メソッドで読み取る必要があります。
from kivy.uix.boxlayout import BoxLayout
上記のコマンドを実行すると、乱数のタイトル、_
プレースホルダー、およびクリック可能なgenerate
ボタンを備えたシンプルなインターフェイスが表示されます。
レンダリングされた乱数アプリ
Kivyファイルを機能させるために何もインポートする必要がなかったことに注意してください。基本的に、アプリを実行するboxlayout
と、クラスと同じ名前のKivyファイル内のファイルを検索して戻ります。これはシンプルなインターフェースであり、アプリを必要に応じて堅牢にすることができます。Kv言語のドキュメントを必ず確認してください。
アプリがほぼ完成したので、ユーザーがgenerate
ボタンをクリックしたときに乱数を生成し、その乱数をアプリのインターフェイスにレンダリングする簡単な関数が必要になります。そのためには、ファイル内のいくつかの変更を行う必要があります。
まず、で乱数を生成するために使用するモジュールをインポートしますimport random
。次に、生成された番号を呼び出す関数またはメソッドを作成します。このデモでは、私は間の範囲を使用します0
と2000
。このrandom.randint(0, 2000)
コマンドを使用すると、乱数を簡単に生成できます。これをすぐにコードに追加します。
次に、独自のバージョンとなる別のクラスを作成しますbox layout
。このbox layout
クラスは、乱数を生成してインターフェイス上でレンダリングするメソッドを含むクラスを継承する必要があります。
class MyRoot(BoxLayout):
def __init__(self):
super(MyRoot, self).__init__()
そのクラス内で、generate
乱数を生成するだけでなく、Kivyファイルに乱数として表示されるものを制御するラベルを操作するメソッドを作成します。
この方法に対応するには、最初に.kv
ファイルに変更を加える必要があります。以来MyRoot
クラスが継承しているbox layout
、あなたが作ることができるMyRoot
あなたのトップレベルの要素.kv
ファイルを:
<MyRoot>:
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
でインデントされたすべてのUI仕様を保持していることに注意してくださいBox Layout
。この後、生成された番号を保持するIDをラベルに追加して、generate
関数が呼び出されたときに簡単に操作できるようにする必要があります。このファイルのIDと、上部のメインコードの別のIDとの関係を、次のBoxLayout
行の直前に指定する必要があります。
<MyRoot>:
random_label: random_label
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
id: random_label
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
このrandom_label: random_label
行は基本的に、IDrandom_label
を持つラベルがファイルrandom_label
内にマップされることをmain.py
意味します。つまり、操作random_label
するアクションはすべて、指定された名前のラベルにマップされます。
これで、メインファイルに乱数を生成するメソッドを作成できます。
def generate_number(self):
self.random_label.text = str(random.randint(0, 2000))
# notice how the class method manipulates the text attributre of the random label by a# ssigning it a new random number generate by the 'random.randint(0, 2000)' funcion. S# ince this the random number generated is an integer, typecasting is required to make # it a string otherwise you will get a typeError in your terminal when you run it.
MyRoot
このクラスは、以下のコードのようになります。
class MyRoot(BoxLayout):
def __init__(self):
super(MyRoot, self).__init__()
def generate_number(self):
self.random_label.text = str(random.randint(0, 2000))
おめでとう!これで、アプリのメインファイルが完成しました。あとは、generate
ボタンがクリックされたときに必ずこの関数を呼び出すようにしてください。ファイルのon_press: root.generate_number()
ボタン選択部分に行を追加するだけで済み.kv
ます。
<MyRoot>:
random_label: random_label
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
id: random_label
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
on_press: root.generate_number()
これで、アプリを実行できます。
Androidでアプリをコンパイルする前に、Windowsユーザーにとって悪いニュースがあります。Androidアプリケーションをコンパイルするには、LinuxまたはmacOSが必要です。ただし、個別のLinuxディストリビューションを用意する必要はなく、代わりに仮想マシンを使用できます。
完全なAndroid.apk
アプリケーションをコンパイルして生成するには、Buildozerというツールを使用します。以下のコマンドのいずれかを使用して、ターミナルからBuildozerをインストールしましょう。
pip3 install buildozer
//
pip install buildozer
次に、Buildozerに必要な依存関係のいくつかをインストールします。私はLinuxErgoを使用しているので、Linux固有のコマンドを使用します。これらのコマンドを1つずつ実行する必要があります。
sudo apt update
sudo apt install -y git zip unzip openjdk-13-jdk python3-pip autoconf libtool pkg-config zlib1g-dev libncurses5-dev libncursesw5-dev libtinfo5 cmake libffi-dev libssl-dev
pip3 install --upgrade Cython==0.29.19 virtualenv
# add the following line at the end of your ~/.bashrc file
export PATH=$PATH:~/.local/bin/
特定のコマンドを実行した後、を実行しbuildozer init
ます。以下のスクリーンショットのような出力が表示されます。
Buildozerの初期化が成功しました
上記のコマンドはBuildozer.spec
ファイルを作成します。このファイルを使用して、アプリの名前やアイコンなどをアプリに指定.spec
できます。ファイルは次のコードブロックのようになります。
[app]
# (str) Title of your application
title = My Application
# (str) Package name
package.name = myapp
# (str) Package domain (needed for android/ios packaging)
package.domain = org.test
# (str) Source code where the main.py live
source.dir = .
# (list) Source files to include (let empty to include all the files)
source.include_exts = py,png,jpg,kv,atlas
# (list) List of inclusions using pattern matching
#source.include_patterns = assets/*,images/*.png
# (list) Source files to exclude (let empty to not exclude anything)
#source.exclude_exts = spec
# (list) List of directory to exclude (let empty to not exclude anything)
#source.exclude_dirs = tests, bin
# (list) List of exclusions using pattern matching
#source.exclude_patterns = license,images/*/*.jpg
# (str) Application versioning (method 1)
version = 0.1
# (str) Application versioning (method 2)
# version.regex = __version__ = \['"\](.*)['"]
# version.filename = %(source.dir)s/main.py
# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy
# (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes
# requirements.source.kivy = ../../kivy
# (list) Garden requirements
#garden_requirements =
# (str) Presplash of the application
#presplash.filename = %(source.dir)s/data/presplash.png
# (str) Icon of the application
#icon.filename = %(source.dir)s/data/icon.png
# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
orientation = portrait
# (list) List of service to declare
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY
#
# OSX Specific
#
#
# author = © Copyright Info
# change the major version of python used by the app
osx.python_version = 3
# Kivy version to use
osx.kivy_version = 1.9.1
#
# Android specific
#
# (bool) Indicate if the application should be fullscreen or not
fullscreen = 0
# (string) Presplash background color (for new android toolchain)
# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
# olive, purple, silver, teal.
#android.presplash_color = #FFFFFF
# (list) Permissions
#android.permissions = INTERNET
# (int) Target Android API, should be as high as possible.
#android.api = 27
# (int) Minimum API your APK will support.
#android.minapi = 21
# (int) Android SDK version to use
#android.sdk = 20
# (str) Android NDK version to use
#android.ndk = 19b
# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
#android.ndk_api = 21
# (bool) Use --private data storage (True) or --dir public storage (False)
#android.private_storage = True
# (str) Android NDK directory (if empty, it will be automatically downloaded.)
#android.ndk_path =
# (str) Android SDK directory (if empty, it will be automatically downloaded.)
#android.sdk_path =
# (str) ANT directory (if empty, it will be automatically downloaded.)
#android.ant_path =
# (bool) If True, then skip trying to update the Android sdk
# This can be useful to avoid excess Internet downloads or save time
# when an update is due and you just want to test/build your package
# android.skip_update = False
# (bool) If True, then automatically accept SDK license
# agreements. This is intended for automation only. If set to False,
# the default, you will be shown the license when first running
# buildozer.
# android.accept_sdk_license = False
# (str) Android entry point, default is ok for Kivy-based app
#android.entrypoint = org.renpy.android.PythonActivity
# (str) Android app theme, default is ok for Kivy-based app
# android.apptheme = "@android:style/Theme.NoTitleBar"
# (list) Pattern to whitelist for the whole project
#android.whitelist =
# (str) Path to a custom whitelist file
#android.whitelist_src =
# (str) Path to a custom blacklist file
#android.blacklist_src =
# (list) List of Java .jar files to add to the libs so that pyjnius can access
# their classes. Don't add jars that you do not need, since extra jars can slow
# down the build process. Allows wildcards matching, for example:
# OUYA-ODK/libs/*.jar
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
#android.add_src =
# (list) Android AAR archives to add (currently works only with sdl2_gradle
# bootstrap)
#android.add_aars =
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
#android.gradle_dependencies =
# (list) add java compile options
# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option
# see https://developer.android.com/studio/write/java8-support for further information
# android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"
# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
# please enclose in double quotes
# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }"
#android.add_gradle_repositories =
# (list) packaging options to add
# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
# can be necessary to solve conflicts in gradle_dependencies
# please enclose in double quotes
# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'"
#android.add_gradle_repositories =
# (list) Java classes to add as activities to the manifest.
#android.add_activities = com.example.ExampleActivity
# (str) OUYA Console category. Should be one of GAME or APP
# If you leave this blank, OUYA support will not be enabled
#android.ouya.category = GAME
# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =
# (str) launchMode to set for the main activity
#android.manifest.launch_mode = standard
# (list) Android additional libraries to copy into libs/armeabi
#android.add_libs_armeabi = libs/android/*.so
#android.add_libs_armeabi_v7a = libs/android-v7/*.so
#android.add_libs_arm64_v8a = libs/android-v8/*.so
#android.add_libs_x86 = libs/android-x86/*.so
#android.add_libs_mips = libs/android-mips/*.so
# (bool) Indicate whether the screen should stay on
# Don't forget to add the WAKE_LOCK permission if you set this to True
#android.wakelock = False
# (list) Android application meta-data to set (key=value format)
#android.meta_data =
# (list) Android library project to add (will be added in the
# project.properties automatically.)
#android.library_references =
# (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
#android.uses_library =
# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D
# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1
# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
android.arch = armeabi-v7a
# (int) overrides automatic versionCode computation (used in build.gradle)
# this is not the same as app version and should only be edited if you know what you're doing
# android.numeric_version = 1
#
# Python for android (p4a) specific
#
# (str) python-for-android fork to use, defaults to upstream (kivy)
#p4a.fork = kivy
# (str) python-for-android branch to use, defaults to master
#p4a.branch = master
# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
#p4a.source_dir =
# (str) The directory in which python-for-android should look for your own build recipes (if any)
#p4a.local_recipes =
# (str) Filename to the hook for p4a
#p4a.hook =
# (str) Bootstrap to use for android builds
# p4a.bootstrap = sdl2
# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
#p4a.port =
#
# iOS specific
#
# (str) Path to a custom kivy-ios folder
#ios.kivy_ios_dir = ../kivy-ios
# Alternately, specify the URL and branch of a git checkout:
ios.kivy_ios_url = https://github.com/kivy/kivy-ios
ios.kivy_ios_branch = master
# Another platform dependency: ios-deploy
# Uncomment to use a custom checkout
#ios.ios_deploy_dir = ../ios_deploy
# Or specify URL and branch
ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
ios.ios_deploy_branch = 1.7.0
# (str) Name of the certificate to use for signing the debug version
# Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"
# (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s
[buildozer]
# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
log_level = 2
# (int) Display warning if buildozer is run as root (0 = False, 1 = True)
warn_on_root = 1
# (str) Path to build artifact storage, absolute or relative to spec file
# build_dir = ./.buildozer
# (str) Path to build output (i.e. .apk, .ipa) storage
# bin_dir = ./bin
# -----------------------------------------------------------------------------
# List as sections
#
# You can define all the "list" as [section:key].
# Each line will be considered as a option to the list.
# Let's take [app] / source.exclude_patterns.
# Instead of doing:
#
#[app]
#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
#
# This can be translated into:
#
#[app:source.exclude_patterns]
#license
#data/audio/*.wav
#data/images/original/*
#
# -----------------------------------------------------------------------------
# Profiles
#
# You can extend section / key with a profile
# For example, you want to deploy a demo version of your application without
# HD content. You could first change the title to add "(demo)" in the name
# and extend the excluded directories to remove the HD content.
#
#[app@demo]
#title = My Application (demo)
#
#[app:source.exclude_patterns@demo]
#images/hd/*
#
# Then, invoke the command line with the "demo" profile:
#
#buildozer --profile demo android debug
アイコン、要件、ロード画面などを指定する場合は、このファイルを編集する必要があります。アプリケーションに必要なすべての編集を行った後buildozer -v android debug
、アプリディレクトリから実行して、アプリケーションをビルドおよびコンパイルします。特に低速のマシンを使用している場合は、これに時間がかかることがあります。
プロセスが完了すると、端末にいくつかのログが表示され、ビルドが成功したことを確認できます。
Androidの成功したビルド
また、binディレクトリにアプリのAPKバージョンが必要です。これは、携帯電話にインストールして実行するアプリケーションの実行可能ファイルです。
binディレクトリのAndroid.apk
おめでとう!このチュートリアルをステップバイステップで実行した場合は、電話に単純な乱数ジェネレーターアプリがインストールされているはずです。それをいじって、いくつかの値を微調整してから、再構築してください。再構築の実行は、最初のビルドほど時間はかかりません。
ご覧のとおり、Pythonを使用したモバイルアプリケーションの構築は、使用しているフレームワークまたはモジュールに精通している限り、かなり簡単です。とにかく、ロジックは同じ方法で実行されます。
Kivyモジュールとそのウィジェットに慣れてください。すべてを一度に知ることはできません。プロジェクトを見つけて、できるだけ早く足を濡らすだけです。ハッピーコーディング。
リンク:https://blog.logrocket.com/build-android-application-kivy-python-framework/
1642405260
If you’re a Python developer thinking about getting started with mobile development, then the Kivy framework is your best bet. With Kivy, you can develop platform-independent applications that compile for iOS, Android, Windows, macOS, and Linux. In this article, we’ll cover Android specifically because it is the most used.
We’ll build a simple random number generator app that you can install on your phone and test when you are done. To follow along with this article, you should be familiar with Python. Let’s get started!
First, you’ll need a new directory for your app. Make sure you have Python installed on your machine and open a new Python file. You’ll need to install the Kivy module from your terminal using either of the commands below. To avoid any package conflicts, be sure you’re installing Kivy in a virtual environment:
pip install kivy
//
pip3 install kivy
Once you have installed Kivy, you should see a success message from your terminal that looks like the screenshots below:
Kivy installation
Successful Kivy installation
Next, navigate into your project folder. In the main.py
file, we’ll need to import the Kivy module and specify which version we want. You can use Kivy v2.0.0, but if you have a smartphone that is older than Android 8.0, I recommend using Kivy v1.9.0. You can mess around with the different versions during the build to see the differences in features and performance.
Add the version number right after the import kivy
line as follows:
kivy.require('1.9.0')
Now, we’ll create a class that will basically define our app; I’ll name mine RandomNumber
. This class will inherit the app
class from Kivy. Therefore, you need to import the app
by adding from kivy.app import App
:
class RandomNumber(App):
In the RandomNumber
class, you’ll need to add a function called build
, which takes a self
parameter. To actually return the UI, we’ll use the build
function. For now, I have it returned as a simple label. To do so, you’ll need to import Label
using the line from kivy.uix.label import Label
:
import kivy
from kivy.app import App
from kivy.uix.label import Label
class RandomNumber(App):
def build(self):
return Label(text="Random Number Generator")
Now, our app skeleton is complete! Before moving forward, you should create an instance of the RandomNumber
class and run it in your terminal or IDE to see the interface:
import kivy from kivy.app import App from kivy.uix.label import Label class RandomNumber(App): def build(self): return Label(text="Random Number Generator") randomApp = RandomNumber() randomApp.run()
When you run the class instance with the text Random Number Generator
, you should see a simple interface or window that looks like the screenshot below:
Simple interface after running the code
You won’t be able to run the text on Android until you’ve finished building the whole thing.
Next, we’ll need a way to outsource the interface. First, we’ll create a Kivy file in our directory that will house most of our design work. You’ll want to name this file the same name as your class using lowercase letters and a .kv
extension. Kivy will automatically associate the class name and the file name, but it may not work on Android if they are exactly the same.
Inside that .kv
file, you need to specify the layout for your app, including elements like the label, buttons, forms, etc. To keep this demonstration simple, I’ll add a label for the title Random Number
, a label that will serve as a placeholder for the random number that is generated _
, and a Generate
button that calls the generate
function.
My .kv
file looks like the code below, but you can mess around with the different values to fit your requirements:
<boxLayout>:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
In the main.py
file, you no longer need the Label
import statement because the Kivy file takes care of your UI. However, you do need to import boxlayout
, which you will use in the Kivy file.
In your main file, you need to add the import statement and edit your main.py
file to read return BoxLayout()
in the build
method:
from kivy.uix.boxlayout import BoxLayout
If you run the command above, you should see a simple interface that has the random number title, the _
place holder, and the clickable generate
button:
Random Number app rendered
Notice that you didn’t have to import anything for the Kivy file to work. Basically, when you run the app, it returns boxlayout
by looking for a file inside the Kivy file with the same name as your class. Keep in mind, this is a simple interface, and you can make your app as robust as you want. Be sure to check out the Kv language documentation.
Now that our app is almost done, we’ll need a simple function to generate random numbers when a user clicks the generate
button, then render that random number into the app interface. To do so, we’ll need to change a few things in our files.
First, we’ll import the module that we’ll use to generate a random number with import random
. Then, we’ll create a function or method that calls the generated number. For this demonstration, I’ll use a range between 0
and 2000
. Generating the random number is simple with the random.randint(0, 2000)
command. We’ll add this into our code in a moment.
Next, we’ll create another class that will be our own version of the box layout
. Our class will have to inherit the box layout
class, which houses the method to generate random numbers and render them on the interface:
class MyRoot(BoxLayout):
def __init__(self):
super(MyRoot, self).__init__()
Within that class, we’ll create the generate
method, which will not only generate random numbers but also manipulate the label that controls what is displayed as the random number in the Kivy file.
To accommodate this method, we’ll first need to make changes to the .kv
file . Since the MyRoot
class has inherited the box layout
, you can make MyRoot
the top level element in your .kv
file:
<MyRoot>:
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
Notice that you are still keeping all your UI specifications indented in the Box Layout
. After this, you need to add an ID to the label that will hold the generated numbers, making it easy to manipulate when the generate
function is called. You need to specify the relationship between the ID in this file and another in the main code at the top, just before the BoxLayout
line:
<MyRoot>:
random_label: random_label
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
id: random_label
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
The random_label: random_label
line basically means that the label with the ID random_label
will be mapped to random_label
in the main.py
file, meaning that any action that manipulates random_label
will be mapped on the label with the specified name.
We can now create the method to generate the random number in the main file:
def generate_number(self):
self.random_label.text = str(random.randint(0, 2000))
# notice how the class method manipulates the text attributre of the random label by a# ssigning it a new random number generate by the 'random.randint(0, 2000)' funcion. S# ince this the random number generated is an integer, typecasting is required to make # it a string otherwise you will get a typeError in your terminal when you run it.
The MyRoot
class should look like the code below:
class MyRoot(BoxLayout):
def __init__(self):
super(MyRoot, self).__init__()
def generate_number(self):
self.random_label.text = str(random.randint(0, 2000))
Congratulations! You’re now done with the main file of the app. The only thing left to do is make sure that you call this function when the generate
button is clicked. You need only add the line on_press: root.generate_number()
to the button selection part of your .kv
file:
<MyRoot>:
random_label: random_label
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
id: random_label
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
on_press: root.generate_number()
Now, you can run the app.
Before compiling our app on Android, I have some bad news for Windows users. You’ll need Linux or macOS to compile your Android application. However, you don’t need to have a separate Linux distribution, instead, you can use a virtual machine.
To compile and generate a full Android .apk
application, we’ll use a tool called Buildozer. Let’s install Buildozer through our terminal using one of the commands below:
pip3 install buildozer
//
pip install buildozer
Now, we’ll install some of Buildozer’s required dependencies. I am on Linux Ergo, so I’ll use Linux-specific commands. You should execute these commands one by one:
sudo apt update
sudo apt install -y git zip unzip openjdk-13-jdk python3-pip autoconf libtool pkg-config zlib1g-dev libncurses5-dev libncursesw5-dev libtinfo5 cmake libffi-dev libssl-dev
pip3 install --upgrade Cython==0.29.19 virtualenv
# add the following line at the end of your ~/.bashrc file
export PATH=$PATH:~/.local/bin/
After executing the specific commands, run buildozer init
. You should see an output similar to the screenshot below:
Buildozer successful initialization
The command above creates a Buildozer .spec
file, which you can use to make specifications to your app, including the name of the app, the icon, etc. The .spec
file should look like the code block below:
[app]
# (str) Title of your application
title = My Application
# (str) Package name
package.name = myapp
# (str) Package domain (needed for android/ios packaging)
package.domain = org.test
# (str) Source code where the main.py live
source.dir = .
# (list) Source files to include (let empty to include all the files)
source.include_exts = py,png,jpg,kv,atlas
# (list) List of inclusions using pattern matching
#source.include_patterns = assets/*,images/*.png
# (list) Source files to exclude (let empty to not exclude anything)
#source.exclude_exts = spec
# (list) List of directory to exclude (let empty to not exclude anything)
#source.exclude_dirs = tests, bin
# (list) List of exclusions using pattern matching
#source.exclude_patterns = license,images/*/*.jpg
# (str) Application versioning (method 1)
version = 0.1
# (str) Application versioning (method 2)
# version.regex = __version__ = \['"\](.*)['"]
# version.filename = %(source.dir)s/main.py
# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy
# (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes
# requirements.source.kivy = ../../kivy
# (list) Garden requirements
#garden_requirements =
# (str) Presplash of the application
#presplash.filename = %(source.dir)s/data/presplash.png
# (str) Icon of the application
#icon.filename = %(source.dir)s/data/icon.png
# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
orientation = portrait
# (list) List of service to declare
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY
#
# OSX Specific
#
#
# author = © Copyright Info
# change the major version of python used by the app
osx.python_version = 3
# Kivy version to use
osx.kivy_version = 1.9.1
#
# Android specific
#
# (bool) Indicate if the application should be fullscreen or not
fullscreen = 0
# (string) Presplash background color (for new android toolchain)
# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
# olive, purple, silver, teal.
#android.presplash_color = #FFFFFF
# (list) Permissions
#android.permissions = INTERNET
# (int) Target Android API, should be as high as possible.
#android.api = 27
# (int) Minimum API your APK will support.
#android.minapi = 21
# (int) Android SDK version to use
#android.sdk = 20
# (str) Android NDK version to use
#android.ndk = 19b
# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
#android.ndk_api = 21
# (bool) Use --private data storage (True) or --dir public storage (False)
#android.private_storage = True
# (str) Android NDK directory (if empty, it will be automatically downloaded.)
#android.ndk_path =
# (str) Android SDK directory (if empty, it will be automatically downloaded.)
#android.sdk_path =
# (str) ANT directory (if empty, it will be automatically downloaded.)
#android.ant_path =
# (bool) If True, then skip trying to update the Android sdk
# This can be useful to avoid excess Internet downloads or save time
# when an update is due and you just want to test/build your package
# android.skip_update = False
# (bool) If True, then automatically accept SDK license
# agreements. This is intended for automation only. If set to False,
# the default, you will be shown the license when first running
# buildozer.
# android.accept_sdk_license = False
# (str) Android entry point, default is ok for Kivy-based app
#android.entrypoint = org.renpy.android.PythonActivity
# (str) Android app theme, default is ok for Kivy-based app
# android.apptheme = "@android:style/Theme.NoTitleBar"
# (list) Pattern to whitelist for the whole project
#android.whitelist =
# (str) Path to a custom whitelist file
#android.whitelist_src =
# (str) Path to a custom blacklist file
#android.blacklist_src =
# (list) List of Java .jar files to add to the libs so that pyjnius can access
# their classes. Don't add jars that you do not need, since extra jars can slow
# down the build process. Allows wildcards matching, for example:
# OUYA-ODK/libs/*.jar
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
#android.add_src =
# (list) Android AAR archives to add (currently works only with sdl2_gradle
# bootstrap)
#android.add_aars =
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
#android.gradle_dependencies =
# (list) add java compile options
# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option
# see https://developer.android.com/studio/write/java8-support for further information
# android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"
# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
# please enclose in double quotes
# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }"
#android.add_gradle_repositories =
# (list) packaging options to add
# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
# can be necessary to solve conflicts in gradle_dependencies
# please enclose in double quotes
# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'"
#android.add_gradle_repositories =
# (list) Java classes to add as activities to the manifest.
#android.add_activities = com.example.ExampleActivity
# (str) OUYA Console category. Should be one of GAME or APP
# If you leave this blank, OUYA support will not be enabled
#android.ouya.category = GAME
# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =
# (str) launchMode to set for the main activity
#android.manifest.launch_mode = standard
# (list) Android additional libraries to copy into libs/armeabi
#android.add_libs_armeabi = libs/android/*.so
#android.add_libs_armeabi_v7a = libs/android-v7/*.so
#android.add_libs_arm64_v8a = libs/android-v8/*.so
#android.add_libs_x86 = libs/android-x86/*.so
#android.add_libs_mips = libs/android-mips/*.so
# (bool) Indicate whether the screen should stay on
# Don't forget to add the WAKE_LOCK permission if you set this to True
#android.wakelock = False
# (list) Android application meta-data to set (key=value format)
#android.meta_data =
# (list) Android library project to add (will be added in the
# project.properties automatically.)
#android.library_references =
# (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
#android.uses_library =
# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D
# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1
# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
android.arch = armeabi-v7a
# (int) overrides automatic versionCode computation (used in build.gradle)
# this is not the same as app version and should only be edited if you know what you're doing
# android.numeric_version = 1
#
# Python for android (p4a) specific
#
# (str) python-for-android fork to use, defaults to upstream (kivy)
#p4a.fork = kivy
# (str) python-for-android branch to use, defaults to master
#p4a.branch = master
# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
#p4a.source_dir =
# (str) The directory in which python-for-android should look for your own build recipes (if any)
#p4a.local_recipes =
# (str) Filename to the hook for p4a
#p4a.hook =
# (str) Bootstrap to use for android builds
# p4a.bootstrap = sdl2
# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
#p4a.port =
#
# iOS specific
#
# (str) Path to a custom kivy-ios folder
#ios.kivy_ios_dir = ../kivy-ios
# Alternately, specify the URL and branch of a git checkout:
ios.kivy_ios_url = https://github.com/kivy/kivy-ios
ios.kivy_ios_branch = master
# Another platform dependency: ios-deploy
# Uncomment to use a custom checkout
#ios.ios_deploy_dir = ../ios_deploy
# Or specify URL and branch
ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
ios.ios_deploy_branch = 1.7.0
# (str) Name of the certificate to use for signing the debug version
# Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"
# (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s
[buildozer]
# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
log_level = 2
# (int) Display warning if buildozer is run as root (0 = False, 1 = True)
warn_on_root = 1
# (str) Path to build artifact storage, absolute or relative to spec file
# build_dir = ./.buildozer
# (str) Path to build output (i.e. .apk, .ipa) storage
# bin_dir = ./bin
# -----------------------------------------------------------------------------
# List as sections
#
# You can define all the "list" as [section:key].
# Each line will be considered as a option to the list.
# Let's take [app] / source.exclude_patterns.
# Instead of doing:
#
#[app]
#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
#
# This can be translated into:
#
#[app:source.exclude_patterns]
#license
#data/audio/*.wav
#data/images/original/*
#
# -----------------------------------------------------------------------------
# Profiles
#
# You can extend section / key with a profile
# For example, you want to deploy a demo version of your application without
# HD content. You could first change the title to add "(demo)" in the name
# and extend the excluded directories to remove the HD content.
#
#[app@demo]
#title = My Application (demo)
#
#[app:source.exclude_patterns@demo]
#images/hd/*
#
# Then, invoke the command line with the "demo" profile:
#
#buildozer --profile demo android debug
If you want to specify things like the icon, requirements, loading screen, etc., you should edit this file. After making all the desired edits to your application, run buildozer -v android debug
from your app directory to build and compile your application. This may take a while, especially if you have a slow machine.
After the process is done, your terminal should have some logs, one confirming that the build was successful:
Android successful build
You should also have an APK version of your app in your bin directory. This is the application executable that you will install and run on your phone:
Android .apk in the bin directory
Congratulations! If you have followed this tutorial step by step, you should have a simple random number generator app on your phone. Play around with it and tweak some values, then rebuild. Running the rebuild will not take as much time as the first build.
As you can see, building a mobile application with Python is fairly straightforward, as long as you are familiar with the framework or module you are working with. Regardless, the logic is executed the same way.
Get familiar with the Kivy module and it’s widgets. You can never know everything all at once. You only need to find a project and get your feet wet as early as possible. Happy coding.
Link: https://blog.logrocket.com/build-android-application-kivy-python-framework/