Design Library Pixelnetica™ Document Scanning SDK for Android
The design
library contains a set of ready-to-use UI controls.
Include in the project
Put into your module-level build script:
dependencies {
implementation("com.pixelnetica.sdk:design:3.0.1")
.....
}
Or, if you use a Version Catalog:
dependencies {
implementation(libs.pixelnetica.design)
.....
}
Usage
To add these controls to your application, follow these steps:
- Inherit an activity that contains these controls from
AppCompatActivity
(or a derived class). - Inherit your application (or activity) theme from
Theme.ScanningSdk
(see the remark below if it is not possible). -
Define the following colors in your theme and change their color values as desired:
<!-- NOTE: Theme "MyApp" derives "Theme.ScanningSdk" --> <style name="Theme.ScanningSdk.MyApp"> <item name="colorPrimary">#FF616161</item> <item name="colorPrimaryContainer">#FF3f51b5</item> <item name="colorSecondary">#FF3f51b5</item> <item name="android:colorBackground">#FFF5F5F5</item> <item name="colorOnBackground">#de000000</item> <item name="colorOnSurfaceVariant">#FF616161</item> </style>
-
If it is not possible to inherit your theme directly from
Theme.ScanningSdk
, you can define a theme inherited fromTheme.ScanningSdk
and reference your main theme as shown below:<style name="MyAppTheme" parent="...."> <!-- Add reference to your ScanningSdk theme --> <item name="isScanningSdkTheme">@Theme.ScanningSdk.MyApp</item> ..... </style> <style name="Theme.ScanningSdk.MyApp"> <!-- As shown above --> ..... </style>
-
If you use a Compose application, add the following to your Compose theme to support screen mode changing and dynamic colors:
@Composable fun MyAppTheme(.....) { ..... // Apply dynamic colors to ScanningSDK before changing night mode ScanningSdkLibrary.enableDynamicColors(dynamicColor) // Apply dark theme to XML layouts AppCompatDelegate.setDefaultNightMode( if (darkTheme) { AppCompatDelegate.MODE_NIGHT_YES } else { AppCompatDelegate.MODE_NIGHT_NO } ) ..... }
Using CropFragment
with XML Views
This component can be used to display images, manually edit document boundaries, and rotate images using XML Views.
Perform the following:
-
Insert the fragment into your layout. For example:
<!-- android:tag is optional. You can use any other method to identify the fragment. --> <androidx.fragment.app.FragmentContainerView android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="match_parent" android:name="com.pixelnetica.design.crop.CropFragment" android:tag="com.pixelnetica.design.crop.CropFragment" .... />
-
Prepare a class that derives from
CropHandler
. The easiest way is to derive yourViewModel
fromCropHandler
. ImplementCropHandler
methods and properties:-
val picture: Flow<ScanPicture?>
Provides a flow with pictures to show ornull
to show nothing. The orientation of the picture is ignored. See below. -
val orientation: Flow<ScanOrientation?>
Provides a flow with the current orientation of the picture. -
val cutout: Flow<ScanCutout?>
Provides a flow with the current cutout ornull
to show nothing. -
fun onPictureReady(pictureReady: Boolean)
This callback is called when the picture is shown on the screen. -
fun onCutoutChanged(cutout: ScanCutout?)
This callback is called when the current cutout has been changed by a user. You need to update theCropHandler.cutout
flow.
-
-
Attach your
CropHandler
to theCropFragment
by callingCropFragment.setHandler
. For example:// We suppose that viewModel derives CropHandler. // We set the tag in XML. (findFragmentByTag("com.pixelnetica.design.crop.CropFragment") as CropFragment).setHandler(viewModel)
Using CropPicture
with Jetpack Compose
This component can be used to display images, manually edit document boundaries, and rotate images using Jetpack Compose.
You can use the Composable function CropPicture
with arguments similar to those described below:
@Composable
fun CropPicture(
modifier: Modifier = Modifier,
picture: ScanPicture?,
orientation: ScanOrientation?,
cutout: ScanCutout?,
onPictureReady: (Boolean) -> Unit = { },
onCutoutChanged: (ScanCutout?) -> Unit
)
Set up the LanguageManager
This section explains how to configure the initial language source (e.g., a server). This setup is required for language-related UI components (LangFragment
/LanguagesSelector
and ReadFragment
/RecognizedText
).
Prerequisites: The Document Scanning SDK (DSSDK) doesn’t provide a remote server. You need to manage the server yourself and place the language files provided by the DSSDK. More details on how to set up languages on own server.
Store the URL to your language server as urlToServer
.
Before using a language list UI, you need to set up the LanguageManager
. You can access the manager by calling:
val languageManager = LanguageManager.getInstance(context)
Next, you need to specify at least one language source:
-
Load the language list from a remote server
urlToServer
:languageManager.requestServer(urlToServer, 1)
-
Load a language list from application assets. This is useful for the most popular languages, such as English. You can store the language file in the assets of your application:
languageManager.requestAssets(pathToAsset, 2)
The second parameter lets us control the sources is “source code”:
-
Delete all language archives with specified codes. The method can be used when you want to change the language server, and you need to delete archives that came from the old server:
languageManager.deleteArchives(code)
-
Keep archives only with specified codes and delete the rest:
languageManager.ensureArchives(codes)
-
Clean up removed languages to install the predefined (marked with
-->
) languages again:languageManager.deleteRemoved()
When you have set up the Language Repository, you can use it with ScanDetector
and ScanReader
:
-
Use the detector path when you create an instance of
ScanDetector
:launch { val detectorPath = LanguageManager .getInstance(context) .detectorPath .first!! val orientationDetector = ScanDetector(detectorPath) picture.detectOrientation(orientationDetector) }
-
Use the
LanguageStore
(directory plus a set of selected languages) when you create an instance ofScanReader
:launch { val languageStore = LanguageManager .getInstance(context) .languageStore .first val scanReader = ScanReader(languageStore.directory, languageStore.languages) picture.read(scanReader) }
Using the LangFragment
with XML Views
This component can be used to manage and edit language lists using XML Views.
Perform the following:
-
Insert the fragment in your layout. For example:
<!-- android:tag is optional. You can use any other method to identify the fragment. --> <androidx.fragment.app.FragmentContainerView android:id="@+id/fragment_host" android:name="com.pixelnetica.design.lang.ui.LangFragment" android:tag="com.pixelnetica.design.lang.ui.LangFragment" android:layout_width="match_parent" android:layout_height="match_parent" />
-
Set up the desired behavior:
-
Get access to the fragment:
// We have set the tag in XML. languageFragment = findFragmentByTag<LangFragment>("com.pixelnetica.design.lang.ui.LangFragment") as LangFragment
-
You can specify an “exclusive” mode: hide the language list when a user expands archives:
languageFragment.setExclusive(false)
-
You can show or hide the archive list from the code:
languageFragment.showArchives(true)
-
Using the LanguagesSelector
with Jetpack Compose
This component can be used to manage and edit language lists using Jetpack Compose.
You can use the Composable function LanguagesSelector
:
@Composable
fun LanguagesSelector(
modifier: Modifier = Modifier,
exclusiveMode: Boolean = false,
)
You can enable exclusiveMode
to hide the language list when a user expands archives. Only the user can collapse or expand this list. This cannot be done with the API.
Using the ReadFragment
with XML Views
This component enables users to edit recognition results using XML Views.
-
Insert the fragment into your layout. For example:
<!-- android:tag is optional. You can use any other method to identify the fragment. --> <androidx.fragment.app.FragmentContainerView android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="match_parent" android:name="com.pixelnetica.design.read.ReadFragment" android:tag="com.pixelnetica.design.read.ReadFragment" .... />
-
Prepare a class that derives from
ReadHandler
. The easiest way is to derive yourViewModel
fromReadHandler
. ImplementReadHandler
methods and properties:-
val picture: Flow<ScanPicture?>
Provides a flow with pictures to show ornull
to show nothing. -
val lookupRect: Flow<RectF?>
Provides the recognition item rectangle ornull
when recognition is done. You can get this value fromcom.pixelnetica.scanning.ScanReader.ProgressCallback.onProgress
. -
val lookupProgress: Flow<Int>
Provides the recognition state:-1
when the image is analyzed, from0
to70
when words are recognized. You can get this value fromcom.pixelnetica.scanning.ScanReader.ProgressCallback.onProgress
. -
val originalText: Flow<ScanText>
Provides the recognition result. This value is used to restore text after user editing. -
val modifiedText: Flow<ScanText>
Provides text modified by the user. -
fun onCancel()
Called when the user presses theX
button during recognition. -
fun onConfirmRestore(confirmRestore: ConfirmRestore)
Called when a user wants to restore the text back to the original. You can show a question box to the user. -
fun onPictureReady(pictureReady: Boolean)
Called when the picture is shown on the display. -
fun onModifiedTextChanged(modifiedText: ScanText)
Called when the user edits the recognized text. You need to store this text in your application and return it back through themodifiedText
flow.
-
-
Attach your
ReadHandler
to theReadFragment
by callingReadFragment.setHandler()
. For example:// We suppose that viewModel derives ReadHandler. // We have set the tag in XML. (findFragmentByTag("com.pixelnetica.design.read.ReadFragment") as ReadFragment).setHandler(viewModel)
Using the RecognizedText
with Jetpack Compose
This component enables users to edit recognition results using Jetpack Compose.
You can use the Composable function RecognizedText
with arguments similar to those described in the section above:
@Composable
fun RecognizedText(
modifier: Modifier = Modifier,
picture: ScanPicture?,
lookupRect: RectF?,
lookupProgress: Int,
originalText: ScanText,
modifiedText: ScanText,
onCancel: @Composable () -> Unit = { },
onConfirmRestore: @Composable ConfirmRestoreScope.(List<CharSequence>) -> Unit = { },
onPictureReady: @Composable (Boolean) -> Unit = { },
onModifiedTextChanged: @Composable (ScanText) -> Unit = { },
)